From f680df8446eb71359cb3f4b5aad3c3fb95417b35 Mon Sep 17 00:00:00 2001 From: Greg Lind Date: Fri, 5 Apr 2024 15:01:25 -0700 Subject: [PATCH] Transparent path master (#358) * Update ISSUE_TEMPLATE.md Add complexity estimate * update travis configs * update travis configs * move build to travis script * update travis for aws * update travis for aws * update travis for aws * update travis for aws * update travis for aws * update travis for aws * update travis for aws * update travis for aws * update travis for aws * update travis for aws * cleanup initital data * cleanup initital data * cleanup initital data * remove organization_name * remove organization_name * update travis var * update travis var * update travis var * revert keys * fix org * fix org * fix org * fix org * fix org * fix org * fix org * turn off tagged commits for builds * update docker compose with email host * update docker compose with email host * update docker compose with email host * update docker compose with email host * add options function to gateway * update permissions with options * refactor: Allow organization name to be accepted when creating core user * chore: Update initial setup * test: Refactored test cases for organization * fix: Flake8 warnings * Flake8 Error fixes * chore: Flake8 fixes for whitespaces and f-strings * chore: Resolved ContextualVersionConflict * chore: requests dependency version * Changes for Oauth in Initial Script * removed changed in gateway view to allow options methid from service * removed changed in gateway view to allow options methid from service * Commenting out options function for options response of services * email alert message for shipment to user * Use generalised function name * initial commit * added seperate endpoint for update * add more illutratative field in message * remove commented code in html template of shipment alert * modifify html template name to send email alert for shipment * remove conflict * Allow user to subscribe to email alert in Profile * fixed linting * changed shipment id to shipment_uuid * added email_alert_flag to CoreUser Serializer * fixed linting * added boolean field in organisation * Resolved issue in OPTIONS method via Core * TransparentPath/buildly-core/issues/45:add radis field in organization * Added support for multiple email alerts * initial commit * return only org names * removed debugging info * change in request format of email alert endpoint (#53) * change in request format of email alert endpoint * resolved flake-8 error * Added consortium table and its endpoint (#50) * added consortium table and its endpoint * formatted as per flake8 * resolved falke8 error * Added organization types (#56) * Added organization type * Formatting fixes * Corrected test case * Approval email for newly registered users (#57) * Added organization type * Formatting fixes * Corrected test case * Approval email for new users * Resolved flake8 warnings * Fixed issue in tests * Resolved comments * Updated email template for alerts * Updated email template for alerts * Organization names coming from open API as list of names (#60) * Resolve permission issue for "/organization/fetch_orgs/" endpoint (#64) * resolved permission issue for fetch_org endpoint * resloved flake-8 error * Add API endpoint for organization type (#65) * resolved permission issue for fetch_org endpoint * resloved flake-8 error * add endpoint for organization type * corrected comments * change permission level * add create and edit date in organization type * change permission level to only organization admin * Updated configuration for consortium (#70) * Updated configuration for consortium * Flake8 fixes * Configuration for user alert preferences (#73) * Configuration for user alert preferences * Flake8 fixes * Fixes in test cases * update consortium table for organization uuid (#75) * Modifications in email templates (#77) * Changes in email template for environmental alerts * Preferences for email alerts * Updated super admin credentials * change permission level for consortium table (#79) * filter consortium by organization (#81) * update consortium array field (#82) * Changed permission level for consortium (#85) * Changed permission level for consortium * Updated flake8 fixes * create consortium if custody create (#87) * create consortium if custody create * resolved flake8 error * Fix issue for retrieve query by uuid (#88) * Revert "Fix issue for retrieve query by uuid (#88)" This reverts commit d2f78ef4dcf3fc723cffbf08fcac6c6d1d8a214a. * Handled boolean for CORS_ORIGIN_ALLOW_ALL * Updated Bravado Core version * Return response data only for PUT, POST, DELETE (#97) * Added default radius for organization * Allow unlimited line size for request * sensor service email alert for unassigned moving sensor (#105) * squashed migration files * construct shipment_url only when shipment_id is present * remove shipment related from email if shipment is not available * Handle when no custody organization mapped to custodian * Environmental warning timezone. (#108) * Change warning timezone to user's timezone only when core user has timezone * append ('UTC') for UTC timezone * Remove Travis * Remove timestamp from alert messages (#110) * removed 'Captured at' from alert message * stop sending datetime in alert message * Gunicorn timeout configuration * buildly-core gitHub actions (#112) * github action for unit test * update triggers for unit test github action * github action for dev docker image build and push * github action for demo docker image build and push * github action for production docker image build and push * added pre-commit hooks for dev, demo and prod branch * update name for unit_test * Github Actions for Unit Test (#114) * Gunicorn timeout configuration * Fixes done for flake * Remove dev deployment setup * Setup github actions * Bump the pip group group in /requirements with 6 updates Bumps the pip group group in /requirements with 6 updates: | Package | From | To | | --- | --- | --- | | [django](https://github.com/django/django) | `2.2.10` | `3.2.24` | | [django-filter](https://github.com/carltongibson/django-filter) | `2.2.0` | `2.4.0` | | [djangorestframework](https://github.com/encode/django-rest-framework) | `3.9.4` | `3.11.2` | | [requests](https://github.com/psf/requests) | `2.25.0` | `2.31.0` | | [aiohttp](https://github.com/aio-libs/aiohttp) | `3.5.4` | `3.9.2` | | [ipython](https://github.com/ipython/ipython) | `7.2.0` | `8.10.0` | Updates `django` from 2.2.10 to 3.2.24 - [Commits](https://github.com/django/django/compare/2.2.10...3.2.24) Updates `django-filter` from 2.2.0 to 2.4.0 - [Release notes](https://github.com/carltongibson/django-filter/releases) - [Changelog](https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst) - [Commits](https://github.com/carltongibson/django-filter/compare/2.2.0...2.4.0) Updates `djangorestframework` from 3.9.4 to 3.11.2 - [Release notes](https://github.com/encode/django-rest-framework/releases) - [Commits](https://github.com/encode/django-rest-framework/compare/3.9.4...3.11.2) Updates `requests` from 2.25.0 to 2.31.0 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.0...v2.31.0) Updates `aiohttp` from 3.5.4 to 3.9.2 - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.5.4...v3.9.2) Updates `ipython` from 7.2.0 to 8.10.0 - [Release notes](https://github.com/ipython/ipython/releases) - [Commits](https://github.com/ipython/ipython/compare/7.2.0...8.10.0) --- updated-dependencies: - dependency-name: django dependency-type: direct:production dependency-group: pip-security-group - dependency-name: django-filter dependency-type: direct:production dependency-group: pip-security-group - dependency-name: djangorestframework dependency-type: direct:production dependency-group: pip-security-group - dependency-name: requests dependency-type: direct:production dependency-group: pip-security-group - dependency-name: aiohttp dependency-type: direct:production dependency-group: pip-security-group - dependency-name: ipython dependency-type: direct:production dependency-group: pip-security-group ... Signed-off-by: dependabot[bot] * Update dev-build.yml The entire file was commented out, checking in without comments. * Update base.txt Fix 3.9,.2 aiohttp not found error from dependabot * [Security] Bump django from 2.2.10 to 2.2.13 Bumps [django](https://github.com/django/django) from 2.2.10 to 2.2.13. **This update includes security fixes.** - [Release notes](https://github.com/django/django/releases) - [Commits](https://github.com/django/django/compare/2.2.10...2.2.13) Signed-off-by: dependabot-preview[bot] * replace buildly-ui to buildly-react-template * Issue #318: Bump django-oauth-toolkit from 1.3.0 to 1.3.2 (#325) * Create GitHub actions for reviews (#329) * Create GitHub actions for reviews * Add docker build step * Add CodeQL for code analysis (#331) * Create codeql-analysis.yml * Fix review workflow * feat: Create GitHub Actions workflow to publish Docker images (#332) * Create GitHub Actions workflow to publish Docker images * Add build release configuration file * Update README configuration table * fix: Fix the release workflow * fix: Fix the build & release from release workflow * fix: Fix semantic release (#334) * rm toladata text (#336) * [Security] Bump django from 2.2.13 to 2.2.18 (#339) Bumps [django](https://github.com/django/django) from 2.2.13 to 2.2.18. **This update includes security fixes.** - [Release notes](https://github.com/django/django/releases) - [Commits](https://github.com/django/django/compare/2.2.13...2.2.18) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * [Security] Bump django from 2.2.18 to 2.2.24 (#341) * [Security] Bump aiohttp from 3.5.4 to 3.7.4 (#338) * feat: Bump djangorestframework from 3.9.4 to 3.11.2 (#340) * [Security] Bump djangorestframework from 3.9.4 to 3.11.2 Bumps [djangorestframework](https://github.com/encode/django-rest-framework) from 3.9.4 to 3.11.2. **This update includes a security fix.** - [Release notes](https://github.com/encode/django-rest-framework/releases) - [Commits](https://github.com/encode/django-rest-framework/compare/3.9.4...3.11.2) Signed-off-by: dependabot-preview[bot] * Fix issue with drf-yasg schema generator Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: jefmoura * Create CODE_OF_CONDUCT.md (#342) * Update README.md * Bump django from 2.2.24 to 2.2.27 in /requirements (#345) Bumps [django](https://github.com/django/django) from 2.2.24 to 2.2.27. - [Release notes](https://github.com/django/django/releases) - [Commits](https://github.com/django/django/compare/2.2.24...2.2.27) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Upstream changes (Auto Approve, Social Auth, Test cases etc) * login with github * added django template * removed django template * initial commit * remove user groups * updated urls * initial commit * Updated dependency issues * Datamesh update (#11) * Datamesh async fixed * Datamesh join function script * Email alert integration (#13) * Integration - Email Notification * Integration - Email Notification * updated serializer * updated unit test * datamesh join script (#14) * datamesh join script * datamesh join script * comment removed * updated datamesh join script * updated datamesh join script * updated join_record_datamesh function * removed print statement * Partner implementation (#17) * Partner Implementation * removed organization * removed partner organization * removed partner organization * - partner unit test * - updated test fixtures * - removed comment * Resolve permissions for Organization * fix core endpoint issue (#21) * fix core endpoint issue * updated comment * Updated environment variables * Updated environment values * Handle for Gateway response 400 * datamesh join: - product <-> third party tool and - product <-> product_team * completed project tool service datamesh join * completed release service datamesh join * completed dev/partner service datamesh join * Fix Datamesh request issue * updated admin username and password * revert find endpoint fix * updated datamesh join module name * Added script to load datamesh * Handled blank response for join records * datamesh POST/PUT request implementation * updated post request for different use-cases * datamesh implemented delete request * post empty response validation * updated datamesh script for pk * converted into functions * updated put request * updated PUT request for case -> The relation that exists but needs to create join * Allow users to be auto-approved after registration * added fields on Relationship model * model via forward lookup * param service name via forward_lookup * written function for the case - Handles The relation that only needs to update ID or UUID * Get response after a POST and PUT request * refactor the functions * updated func parameter * issue fix - delete join * delete status join * migration file * User Type Implementation (#45) * added user type * admin dashboard * added survey_status * serializer fields added * help text * User profile update API (#48) * user profile update API * updated get_permission * unit test cases * added serializer field * serializer organization_name validation * updated core-user serializer update method * User registration update (#53) * register with org validation * uncommented invitation_token method * updated load initial script for org creation * set auto approve to false * core user create method * default org name * added new var to update * code format * updated gunicorn timeout * removed unwanted files from upstream pr * updated migration files * added new line * removed unwanted changes * removed prepare_get_request() function * updated DEFAULT_ORG to lowercase * Fix None type DEFAULT_ORG issue Co-authored-by: ashishkmishra36 Co-authored-by: Ashish K Mishra <70134840+ashishkmishra36@users.noreply.github.com> Co-authored-by: Yasmin Ansari Co-authored-by: mthombare <83965396+mthombare@users.noreply.github.com> Co-authored-by: manish Co-authored-by: Yasmin Ansari Co-authored-by: Greg Lind * Changes from Insights for login, datamesh and scripts to enhance datamesh (#347) * login with github * added django template * removed django template * initial commit * remove user groups * updated urls * initial commit * Updated dependency issues * Datamesh update (#11) * Datamesh async fixed * Datamesh join function script * Email alert integration (#13) * Integration - Email Notification * Integration - Email Notification * updated serializer * updated unit test * datamesh join script (#14) * datamesh join script * datamesh join script * comment removed * updated datamesh join script * updated datamesh join script * updated join_record_datamesh function * removed print statement * Partner implementation (#17) * Partner Implementation * removed organization * removed partner organization * removed partner organization * - partner unit test * - updated test fixtures * - removed comment * Resolve permissions for Organization * fix core endpoint issue (#21) * fix core endpoint issue * updated comment * Updated environment variables * Updated environment values * Handle for Gateway response 400 * datamesh join: - product <-> third party tool and - product <-> product_team * completed project tool service datamesh join * completed release service datamesh join * completed dev/partner service datamesh join * Fix Datamesh request issue * updated admin username and password * revert find endpoint fix * updated datamesh join module name * Added script to load datamesh * Handled blank response for join records * datamesh POST/PUT request implementation * updated post request for different use-cases * datamesh implemented delete request * post empty response validation * updated datamesh script for pk * converted into functions * updated put request * updated PUT request for case -> The relation that exists but needs to create join * Allow users to be auto-approved after registration * added fields on Relationship model * model via forward lookup * param service name via forward_lookup * written function for the case - Handles The relation that only needs to update ID or UUID * Get response after a POST and PUT request * refactor the functions * updated func parameter * issue fix - delete join * delete status join * migration file * User Type Implementation (#45) * added user type * admin dashboard * added survey_status * serializer fields added * help text * User profile update API (#48) * user profile update API * updated get_permission * unit test cases * added serializer field * serializer organization_name validation * updated core-user serializer update method * User registration update (#53) * register with org validation * uncommented invitation_token method * updated load initial script for org creation * set auto approve to false * core user create method * default org name * added new var to update * code format * updated gunicorn timeout * Datamesh id CRUD Implementation (#56) * create join with ID * updated join script to create relation if json file doesn't exist * added newly added var in script * delete id join * Fixed gateway unit test cases * implemented forward and reverse relation join in PUT req * refactored fk reference variable * written module join script * PUT request: modified prepare_update_request() fun * Fix POST req reverse relation join * updated relation model fields * updated script variable * unit tes case * updated unit test cases * updated join script * Datamesh implementation with core (#59) * implementation with core * PUT request update fk value * script info * added Datamesh relation response data in POST and PUT request * removed unused import * RequestHandler returned response * added core update req data to req response data * Stripe integration for customer create and get card details (#62) * initial stripe subscription changes * add new stripe/products endpoint * change plan to product for subscription * add stripe check for update org for a user scenario * Fixed unit test cases (#63) * GitHub action configuration (#67) * GitHub action configuration * Updated prod-build GCR path * Updated prod-build slack message * Updated prod-build mail message * Added Product Team as default user type (#65) Co-authored-by: ashishkmishra36 Co-authored-by: Ashish K Mishra <70134840+ashishkmishra36@users.noreply.github.com> Co-authored-by: Yasmin Ansari Co-authored-by: mthombare <83965396+mthombare@users.noreply.github.com> Co-authored-by: manish Co-authored-by: Yasmin Ansari Co-authored-by: Radhika Patel * Feat#113/upstream changes (#350) * Update ISSUE_TEMPLATE.md Add complexity estimate * update travis configs * update travis configs * move build to travis script * update travis for aws * update travis for aws * update travis for aws * update travis for aws * update travis for aws * update travis for aws * update travis for aws * update travis for aws * update travis for aws * update travis for aws * cleanup initital data * cleanup initital data * cleanup initital data * remove organization_name * remove organization_name * update travis var * update travis var * update travis var * revert keys * fix org * fix org * fix org * fix org * fix org * fix org * fix org * turn off tagged commits for builds * update docker compose with email host * update docker compose with email host * update docker compose with email host * update docker compose with email host * add options function to gateway * update permissions with options * refactor: Allow organization name to be accepted when creating core user * chore: Update initial setup * test: Refactored test cases for organization * fix: Flake8 warnings * Flake8 Error fixes * chore: Flake8 fixes for whitespaces and f-strings * chore: Resolved ContextualVersionConflict * chore: requests dependency version * Changes for Oauth in Initial Script * removed changed in gateway view to allow options methid from service * removed changed in gateway view to allow options methid from service * Commenting out options function for options response of services * email alert message for shipment to user * Use generalised function name * initial commit * added seperate endpoint for update * add more illutratative field in message * remove commented code in html template of shipment alert * modifify html template name to send email alert for shipment * remove conflict * Allow user to subscribe to email alert in Profile * fixed linting * changed shipment id to shipment_uuid * added email_alert_flag to CoreUser Serializer * fixed linting * added boolean field in organisation * Resolved issue in OPTIONS method via Core * TransparentPath/buildly-core/issues/45:add radis field in organization * Added support for multiple email alerts * initial commit * return only org names * removed debugging info * change in request format of email alert endpoint (#53) * change in request format of email alert endpoint * resolved flake-8 error * Added consortium table and its endpoint (#50) * added consortium table and its endpoint * formatted as per flake8 * resolved falke8 error * Added organization types (#56) * Added organization type * Formatting fixes * Corrected test case * Approval email for newly registered users (#57) * Added organization type * Formatting fixes * Corrected test case * Approval email for new users * Resolved flake8 warnings * Fixed issue in tests * Resolved comments * Updated email template for alerts * Updated email template for alerts * Organization names coming from open API as list of names (#60) * Resolve permission issue for "/organization/fetch_orgs/" endpoint (#64) * resolved permission issue for fetch_org endpoint * resloved flake-8 error * Add API endpoint for organization type (#65) * resolved permission issue for fetch_org endpoint * resloved flake-8 error * add endpoint for organization type * corrected comments * change permission level * add create and edit date in organization type * change permission level to only organization admin * Updated configuration for consortium (#70) * Updated configuration for consortium * Flake8 fixes * Configuration for user alert preferences (#73) * Configuration for user alert preferences * Flake8 fixes * Fixes in test cases * update consortium table for organization uuid (#75) * Modifications in email templates (#77) * Changes in email template for environmental alerts * Preferences for email alerts * Updated super admin credentials * change permission level for consortium table (#79) * filter consortium by organization (#81) * update consortium array field (#82) * Changed permission level for consortium (#85) * Changed permission level for consortium * Updated flake8 fixes * create consortium if custody create (#87) * create consortium if custody create * resolved flake8 error * Fix issue for retrieve query by uuid (#88) * Revert "Fix issue for retrieve query by uuid (#88)" This reverts commit d2f78ef4dcf3fc723cffbf08fcac6c6d1d8a214a. * Handled boolean for CORS_ORIGIN_ALLOW_ALL * Updated Bravado Core version * Return response data only for PUT, POST, DELETE (#97) * Added default radius for organization * Allow unlimited line size for request * sensor service email alert for unassigned moving sensor (#105) * squashed migration files * construct shipment_url only when shipment_id is present * remove shipment related from email if shipment is not available * Handle when no custody organization mapped to custodian * Environmental warning timezone. (#108) * Change warning timezone to user's timezone only when core user has timezone * append ('UTC') for UTC timezone * Remove Travis * Remove timestamp from alert messages (#110) * removed 'Captured at' from alert message * stop sending datetime in alert message * Gunicorn timeout configuration * added new line at the end of gunicorn_conf.py file * added new line at the end of settings/base.py file Co-authored-by: Greg Lind Co-authored-by: Yasmin Ansari Co-authored-by: vishalajackus Co-authored-by: ashishkmishra36 Co-authored-by: vishalajackus <73515569+vishalajackus@users.noreply.github.com> Co-authored-by: Ashish K Mishra <70134840+ashishkmishra36@users.noreply.github.com> Co-authored-by: mthombare <83965396+mthombare@users.noreply.github.com> Co-authored-by: Yasmin Ansari Co-authored-by: RadhikaPPatel * Bump djangorestframework from 3.9.4 to 3.11.2 in /requirements (#352) Bumps [djangorestframework](https://github.com/encode/django-rest-framework) from 3.9.4 to 3.11.2. - [Release notes](https://github.com/encode/django-rest-framework/releases) - [Commits](https://github.com/encode/django-rest-framework/compare/3.9.4...3.11.2) --- updated-dependencies: - dependency-name: djangorestframework dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * clean code to run locally (#354) * clean code to run locally * debugging of docker and other issues Co-authored-by: anamariaroman * Update README.md (#355) Adding vision and objectives * Update README.md * Update issue templates * Merge with massive style changes that should not have been there * remove secrets * fix conflict * fix conflict * fix conflict --------- Signed-off-by: dependabot[bot] Signed-off-by: dependabot-preview[bot] Co-authored-by: Yasmin Ansari Co-authored-by: vishalajackus Co-authored-by: ashishkmishra36 Co-authored-by: vishalajackus <73515569+vishalajackus@users.noreply.github.com> Co-authored-by: Ashish K Mishra <70134840+ashishkmishra36@users.noreply.github.com> Co-authored-by: mthombare <83965396+mthombare@users.noreply.github.com> Co-authored-by: Yasmin Ansari Co-authored-by: abhishek-kumar-piyush <97152893+abhishek-kumar-piyush@users.noreply.github.com> Co-authored-by: RadhikaPPatel Co-authored-by: Radhika Patel Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: JiseonYu Co-authored-by: Jeferson Moura Co-authored-by: jefmoura Co-authored-by: karrla <15875770+karrla@users.noreply.github.com> Co-authored-by: manish Co-authored-by: Peter Odeny Co-authored-by: anamariaroman --- .github/workflows/demo-build.yml | 27 + .github/workflows/dev-build.yml | 5 + .pre-commit-config.yaml | 6 + Dockerfile | 5 +- .../management/commands/loadinitialdata.py | 33 +- buildly/settings/authentication.py | 66 ++- buildly/settings/base.py | 60 +-- buildly/settings/production.py | 12 +- buildly/tests/test_admin.py | 10 +- buildly/tests/test_loadinitialdata.py | 30 +- buildly/wsgi.py | 3 +- conftest.py | 1 - core/admin.py | 12 +- core/auth_pipeline.py | 34 +- core/email_utils.py | 19 +- core/jwt_utils.py | 9 +- core/middleware.py | 7 +- core/migrations/0001_initial.py | 317 ++++++++++++ core/models.py | 181 +++++-- core/permissions.py | 10 +- core/serializers.py | 83 +++- core/swagger.py | 62 +-- core/tests/fixtures.py | 52 +- core/tests/test_accesstokenview.py | 34 +- core/tests/test_applicationview.py | 24 +- core/tests/test_auth_pipeline.py | 87 ++-- core/tests/test_coregroupview.py | 95 ++-- core/tests/test_coreuserview.py | 208 +++++--- core/tests/test_emailtemplates.py | 20 +- core/tests/test_jwt_utils.py | 14 +- core/tests/test_logicmoduleview.py | 12 +- core/tests/test_organizationview.py | 2 +- core/tests/test_permissions.py | 2 - core/tests/test_refreshtokenview.py | 54 +- core/tests/test_serializers.py | 64 +-- core/tests/test_utils.py | 27 +- core/tests/test_views.py | 24 +- core/urls.py | 9 +- core/utils.py | 3 +- core/views/consortium.py | 7 + core/views/coregroup.py | 1 + core/views/coreuser.py | 260 +++++++--- core/views/logicmodule.py | 4 +- core/views/oauth.py | 24 +- core/views/organization.py | 16 + core/views/web.py | 16 +- datamesh/exceptions.py | 1 - datamesh/filters.py | 7 +- .../management/commands/loadrelationships.py | 12 +- datamesh/managers.py | 23 +- datamesh/migrations/0001_initial.py | 100 +++- .../migrations/0002_auto_20190918_1659.py | 73 ++- datamesh/mixins.py | 3 +- datamesh/models.py | 99 ++-- datamesh/serializers.py | 50 +- datamesh/services.py | 78 ++- datamesh/tests/fixtures.py | 117 +++-- datamesh/tests/test_datamesh_service.py | 227 ++++++--- datamesh/tests/test_join.py | 134 +++-- datamesh/tests/test_models.py | 78 ++- datamesh/tests/test_serializers.py | 7 +- datamesh/tests/test_views.py | 258 ++++++---- datamesh/utils.py | 11 +- datamesh/views.py | 21 +- docker-compose.yml | 4 +- docs/conf.py | 29 +- factories/core_models.py | 2 +- factories/datamesh_models.py | 11 +- factories/oauth2_models.py | 3 +- factories/workflow_models.py | 3 +- gateway/aggregator.py | 41 +- gateway/clients.py | 41 +- gateway/generator.py | 25 +- gateway/permissions.py | 28 +- gateway/request.py | 43 +- gateway/tests/fixtures.py | 32 +- gateway/tests/test_permissions.py | 82 +-- gateway/tests/test_swagger_aggregator.py | 21 +- gateway/tests/test_urls.py | 55 +- gateway/tests/test_utils.py | 39 +- gateway/tests/test_views.py | 73 ++- gateway/tests/test_views_async.py | 210 ++++++-- gateway/tests/utils.py | 13 +- gateway/urls.py | 25 +- gateway/utils.py | 23 +- gateway/views.py | 17 +- manage.py | 3 +- requirements.txt | 0 requirements/base.txt | 10 +- requirements/test.txt | 4 + workflow/admin.py | 15 +- workflow/migrations/0001_initial.py | 423 ++++++++++++++-- workflow/models.py | 209 ++++++-- workflow/pagination.py | 6 +- workflow/permissions.py | 59 ++- workflow/serializers.py | 9 +- .../tests/test_internationalizationview.py | 24 +- workflow/tests/test_serializers.py | 42 +- workflow/tests/test_workflowlevel1view.py | 272 ++++++---- .../tests/test_workflowlevel2serializers.py | 5 +- workflow/tests/test_workflowlevel2sortview.py | 140 +++--- workflow/tests/test_workflowlevel2view.py | 469 +++++++++++------- workflow/tests/test_workflowlevelstatus.py | 3 +- workflow/views/workflowlevel1.py | 11 +- workflow/views/workflowlevel2.py | 17 +- workflow/views/workflowlevelstatus.py | 5 +- workflow/views/workflowleveltype.py | 5 +- workflow/views/workflowteam.py | 10 +- 108 files changed, 4137 insertions(+), 1774 deletions(-) create mode 100644 .github/workflows/demo-build.yml create mode 100644 .pre-commit-config.yaml create mode 100644 requirements.txt diff --git a/.github/workflows/demo-build.yml b/.github/workflows/demo-build.yml new file mode 100644 index 00000000..4ca48d36 --- /dev/null +++ b/.github/workflows/demo-build.yml @@ -0,0 +1,27 @@ +name: Build and Push to Demo + +on: + push: + branches: + - demo +jobs: + build: + name: Build and Push to GCR + runs-on: ubuntu-latest + env: + IMAGE_NAME: gcr.io/spry-bricolage-298920/transparent-path/demo/buildly-core + steps: + - uses: actions/checkout@v2 + + - name: Docker login + uses: docker/login-action@v1 + with: + registry: gcr.io + username: _json_key + password: ${{ secrets.GCR_JSON_KEY }} + + - name: Build docker image + run: docker build -t $IMAGE_NAME:latest . + + - name: Push to Google Container Registry + run: docker push $IMAGE_NAME:latest diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index 51f0a569..98406c7c 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -3,7 +3,12 @@ name: Build and Push to Development on: push: branches: +<<<<<<< HEAD + - dev + +======= - master +>>>>>>> master jobs: build: name: Build and Push to GCR diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..b55d74ea --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,6 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: no-commit-to-branch + args: [--branch, prod, --branch, demo, --branch, dev] diff --git a/Dockerfile b/Dockerfile index eaacea4d..2b85fd62 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.7-alpine3.10 +FROM --platform=linux/amd64 python:3.7-alpine3.10 # Do not buffer log messages in memory; some messages can be lost otherwise ENV PYTHONUNBUFFERED 1 @@ -21,5 +21,8 @@ RUN ./scripts/collectstatic.sh RUN apk del .build-deps +# Specify tag name to be created on github +LABEL version="1.0.10" + EXPOSE 8080 ENTRYPOINT ["bash", "/code/scripts/docker-entrypoint.sh"] diff --git a/buildly/management/commands/loadinitialdata.py b/buildly/management/commands/loadinitialdata.py index 8fc53890..033dc328 100644 --- a/buildly/management/commands/loadinitialdata.py +++ b/buildly/management/commands/loadinitialdata.py @@ -5,8 +5,21 @@ from django.core.management.base import BaseCommand from django.db import transaction +<<<<<<< HEAD +from core.models import ( + ROLE_VIEW_ONLY, + ROLE_ORGANIZATION_ADMIN, + ROLE_WORKFLOW_ADMIN, + ROLE_WORKFLOW_TEAM, + Organization, + CoreUser, + CoreGroup, + OrganizationType, +) +======= from core.models import ROLE_VIEW_ONLY, ROLE_ORGANIZATION_ADMIN, ROLE_WORKFLOW_ADMIN, ROLE_WORKFLOW_TEAM, \ Organization, CoreUser, CoreGroup, OrganizationType +>>>>>>> master logger = logging.getLogger(__name__) @@ -34,13 +47,19 @@ def _create_organization_types(self): def _create_default_organization(self): if settings.DEFAULT_ORG: - self._default_org, _ = Organization.objects.get_or_create(name=settings.DEFAULT_ORG) + self._default_org, _ = Organization.objects.get_or_create( + name=settings.DEFAULT_ORG + ) def _create_groups(self): - self._su_group = CoreGroup.objects.filter(is_global=True, permissions=15).first() + self._su_group = CoreGroup.objects.filter( + is_global=True, permissions=15 + ).first() if not self._su_group: logger.info("Creating global CoreGroup") - self._su_group = CoreGroup.objects.create(name='Global Admin', is_global=True, permissions=15) + self._su_group = CoreGroup.objects.create( + name='Global Admin', is_global=True, permissions=15 + ) # TODO: remove this after full Group -> CoreGroup refactoring self._groups.append(Group.objects.get_or_create(name=ROLE_VIEW_ONLY)) @@ -56,7 +75,15 @@ def _create_user(self): logger.info("Creating Super User") user_password = None if settings.DEBUG: +<<<<<<< HEAD + user_password = ( + settings.SUPER_USER_PASSWORD + if settings.SUPER_USER_PASSWORD + else 'zGtkgLvmNiKm' + ) +======= user_password = settings.SUPER_USER_PASSWORD if settings.SUPER_USER_PASSWORD else 'zGtkgLvmNiKm' +>>>>>>> master elif settings.SUPER_USER_PASSWORD: user_password = settings.SUPER_USER_PASSWORD else: diff --git a/buildly/settings/authentication.py b/buildly/settings/authentication.py index c090b253..3a00292f 100644 --- a/buildly/settings/authentication.py +++ b/buildly/settings/authentication.py @@ -31,7 +31,7 @@ # Rest Framework OAuth2 and JWT REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] += [ 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', - 'oauth2_provider_jwt.authentication.JWTAuthentication' + 'oauth2_provider_jwt.authentication.JWTAuthentication', ] # Auth Application @@ -50,28 +50,25 @@ AUTH_PASSWORD_VALIDATORS = [] AUTH_PASSWORD_VALIDATORS_MAP = { - 'USE_PASSWORD_USER_ATTRIBUTE_SIMILARITY_VALIDATOR': - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - 'USE_PASSWORD_MINIMUM_LENGTH_VALIDATOR': - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - 'OPTIONS': { - 'min_length': int(os.getenv('PASSWORD_MINIMUM_LENGTH', 6)), - } - }, - 'USE_PASSWORD_COMMON_VALIDATOR': - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - 'USE_PASSWORD_NUMERIC_VALIDATOR': - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, + 'USE_PASSWORD_USER_ATTRIBUTE_SIMILARITY_VALIDATOR': { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator' + }, + 'USE_PASSWORD_MINIMUM_LENGTH_VALIDATOR': { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + 'OPTIONS': {'min_length': int(os.getenv('PASSWORD_MINIMUM_LENGTH', 6))}, + }, + 'USE_PASSWORD_COMMON_VALIDATOR': { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator' + }, + 'USE_PASSWORD_NUMERIC_VALIDATOR': { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator' + }, } -for password_validator_env_var, password_validator in AUTH_PASSWORD_VALIDATORS_MAP.items(): +for ( + password_validator_env_var, + password_validator, +) in AUTH_PASSWORD_VALIDATORS_MAP.items(): if os.getenv(password_validator_env_var, 'True') == 'True': AUTH_PASSWORD_VALIDATORS.append(password_validator) @@ -81,11 +78,13 @@ SOCIAL_AUTH_URL_NAMESPACE = 'social' SOCIAL_AUTH_POSTGRES_JSONFIELD = True -SOCIAL_AUTH_REDIRECT_IS_HTTPS = True if os.getenv('SOCIAL_AUTH_REDIRECT_IS_HTTPS') == 'True' else False +SOCIAL_AUTH_REDIRECT_IS_HTTPS = ( + True if os.getenv('SOCIAL_AUTH_REDIRECT_IS_HTTPS') == 'True' else False +) SOCIAL_AUTH_LOGIN_REDIRECT_URLS = { 'github': os.getenv('SOCIAL_AUTH_GITHUB_REDIRECT_URL', None), 'google-oauth2': os.getenv('SOCIAL_AUTH_GOOGLE_OAUTH2_REDIRECT_URL', None), - 'microsoft-graph': os.getenv('SOCIAL_AUTH_MICROSOFT_GRAPH_REDIRECT_URL', None) + 'microsoft-graph': os.getenv('SOCIAL_AUTH_MICROSOFT_GRAPH_REDIRECT_URL', None), } SOCIAL_AUTH_PIPELINE = ( @@ -117,9 +116,18 @@ # i.e. ['example.com', 'buildly.io','treeaid.org'] if os.getenv('SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS'): SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS = os.getenv( +<<<<<<< HEAD + 'SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS' + ).split(',') + 'SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS').split(',') + +======= 'SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS').split(',') +>>>>>>> master if os.getenv('SOCIAL_AUTH_MICROSOFT_WHITELISTED_DOMAINS'): - SOCIAL_AUTH_GOOGLE_MICROSOFT_DOMAINS = os.getenv('SOCIAL_AUTH_MICROSOFT_WHITELISTED_DOMAINS').split(',') + SOCIAL_AUTH_GOOGLE_MICROSOFT_DOMAINS = os.getenv( + 'SOCIAL_AUTH_MICROSOFT_WHITELISTED_DOMAINS' + ).split(',') # oauth2 settings OAUTH2_PROVIDER = { @@ -132,7 +140,9 @@ } DEFAULT_OAUTH_DOMAINS = os.getenv('DEFAULT_OAUTH_DOMAINS', '') -CREATE_DEFAULT_PROGRAM = True if os.getenv('CREATE_DEFAULT_PROGRAM') == 'True' else False +CREATE_DEFAULT_PROGRAM = ( + True if os.getenv('CREATE_DEFAULT_PROGRAM') == 'True' else False +) # LDAP configuration # https://django-auth-ldap.readthedocs.io/en/latest/reference.html#settings @@ -147,7 +157,7 @@ AUTH_LDAP_USER_SEARCH = LDAPSearch( AUTH_LDAP_BASE_DN, ldap.SCOPE_SUBTREE, - f'{AUTH_LDAP_USERNAME_FIELD_SEARCH}=%(user)s' + f'{AUTH_LDAP_USERNAME_FIELD_SEARCH}=%(user)s', ) AUTH_LDAP_USER_ATTR_MAP = { @@ -157,4 +167,6 @@ 'email': 'mail', } AUTH_LDAP_ALWAYS_UPDATE_USER = True - AUTH_LDAP_CACHE_TIMEOUT = 3600 # Cache distinguished names and group memberships for an hour to minimize + AUTH_LDAP_CACHE_TIMEOUT = ( + 3600 + ) # Cache distinguished names and group memberships for an hour to minimize diff --git a/buildly/settings/base.py b/buildly/settings/base.py index 728e44fe..1cad59de 100644 --- a/buildly/settings/base.py +++ b/buildly/settings/base.py @@ -1,8 +1,7 @@ import os # Base dir path -BASE_DIR = os.path.dirname( - os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) SECRET_KEY = os.environ['SECRET_KEY'] @@ -20,9 +19,7 @@ STATIC_URL = '/static/' -STATICFILES_DIRS = [ - os.path.join(BASE_DIR, "static"), -] +STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] INSTALLED_APPS_DJANGO = [ @@ -40,31 +37,23 @@ 'django_filters', 'rest_framework', 'rest_framework.authtoken', - # Social auth 'social_django', - # OAuth2 'oauth2_provider', 'oauth2_provider_jwt', - # swagger 'drf_yasg', - # health check - 'health_check', # required - 'health_check.db', # stock Django health checkers + 'health_check', # required + 'health_check.db', # stock Django health checkers ] -INSTALLED_APPS_LOCAL = [ - 'buildly', - 'gateway', - 'core', - 'workflow', - 'datamesh', -] +INSTALLED_APPS_LOCAL = ['buildly', 'gateway', 'core', 'workflow', 'datamesh'] -INSTALLED_APPS = INSTALLED_APPS_DJANGO + INSTALLED_APPS_THIRD_PARTIES + INSTALLED_APPS_LOCAL +INSTALLED_APPS = ( + INSTALLED_APPS_DJANGO + INSTALLED_APPS_THIRD_PARTIES + INSTALLED_APPS_LOCAL +) MIDDLEWARE_DJANGO = [ 'django.middleware.security.SecurityMiddleware', @@ -76,13 +65,9 @@ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] -MIDDLEWARE_CSRF = [ - 'core.middleware.DisableCsrfCheck', -] +MIDDLEWARE_CSRF = ['core.middleware.DisableCsrfCheck'] -EXCEPTION_MIDDLEWARE = [ - 'core.middleware.ExceptionMiddleware' -] +EXCEPTION_MIDDLEWARE = ['core.middleware.ExceptionMiddleware'] MIDDLEWARE = MIDDLEWARE_DJANGO + MIDDLEWARE_CSRF + EXCEPTION_MIDDLEWARE @@ -91,9 +76,7 @@ TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - 'templates', - ], + 'DIRS': ['templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -106,10 +89,10 @@ 'social_django.context_processors.login_redirect', ], 'builtins': [ # TODO to delete? - 'django.contrib.staticfiles.templatetags.staticfiles', + 'django.contrib.staticfiles.templatetags.staticfiles' ], }, - }, + } ] WSGI_APPLICATION = 'buildly.wsgi.application' @@ -171,9 +154,7 @@ 'rest_framework.authentication.SessionAuthentication', # TODO check if disable, and also delete CSRF 'rest_framework.authentication.TokenAuthentication', ], - 'DEFAULT_PERMISSION_CLASSES': ( - 'core.permissions.IsSuperUserBrowseableAPI', - ) + 'DEFAULT_PERMISSION_CLASSES': ('core.permissions.IsSuperUserBrowseableAPI',) # ToDo: Think about `DEFAULT_PAGINATION_CLASS as env variable and # customizable values with reasonable defaults } @@ -181,7 +162,9 @@ # Front-end application URL FRONTEND_URL = os.getenv('FRONTEND_URL', 'http://www.example.com/') REGISTRATION_URL_PATH = os.getenv('REGISTRATION_URL_PATH', 'register/') -RESETPASS_CONFIRM_URL_PATH = os.getenv('RESETPASS_CONFIRM_URL_PATH', 'reset_password_confirm/') +RESETPASS_CONFIRM_URL_PATH = os.getenv( + 'RESETPASS_CONFIRM_URL_PATH', 'reset_password_confirm/' +) PASSWORD_RESET_TIMEOUT_DAYS = 1 @@ -197,11 +180,6 @@ # Swagger settings - for generate_swagger management command -SWAGGER_SETTINGS = { - 'DEFAULT_INFO': 'gateway.urls.swagger_info', -} +SWAGGER_SETTINGS = {'DEFAULT_INFO': 'gateway.urls.swagger_info'} -ORGANIZATION_TYPES = [ - 'Custodian', - 'Producer' -] +ORGANIZATION_TYPES = ['Custodian', 'Producer'] diff --git a/buildly/settings/production.py b/buildly/settings/production.py index 1ba41c5a..184eb65e 100644 --- a/buildly/settings/production.py +++ b/buildly/settings/production.py @@ -5,13 +5,9 @@ # CORS to allow external apps auth through OAuth 2 # https://github.com/ottoyiu/django-cors-headers -INSTALLED_APPS += ( - 'corsheaders', -) +INSTALLED_APPS += ('corsheaders',) -MIDDLEWARE_CORS = [ - 'corsheaders.middleware.CorsMiddleware', -] +MIDDLEWARE_CORS = ['corsheaders.middleware.CorsMiddleware'] MIDDLEWARE = MIDDLEWARE_CORS + MIDDLEWARE @@ -42,13 +38,13 @@ 'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'), 'class': 'logging.FileHandler', 'filename': '/var/log/buildly.log', - }, + } }, 'loggers': { 'django': { 'handlers': ['file'], 'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'), 'propagate': True, - }, + } }, } diff --git a/buildly/tests/test_admin.py b/buildly/tests/test_admin.py index 0c5337a5..1a34931e 100644 --- a/buildly/tests/test_admin.py +++ b/buildly/tests/test_admin.py @@ -19,9 +19,9 @@ def test_admin_user_auth_page_with_superuser(self): def test_admin_user_auth_page_with_staff_user(self): """Staff user shouldn't see superuser status field on django admin""" - staff_user = CoreUser.objects.create_user('staff_user', - 'staffuser@example.com', - 'Password123') + staff_user = CoreUser.objects.create_user( + 'staff_user', 'staffuser@example.com', 'Password123' + ) permission = Permission.objects.get(name='Can change core user') staff_user.user_permissions.add(permission) staff_user.is_staff = True @@ -50,7 +50,9 @@ def test_admin_user_permissions_section_with_superuser(self): def test_admin_user_permissions_section_with_staff_user(self): """Staff user shouldn't see user permissions section field on django admin""" - staff_user = CoreUser.objects.create_user('staff_user', 'staffuser@example.com', 'Password123') + staff_user = CoreUser.objects.create_user( + 'staff_user', 'staffuser@example.com', 'Password123' + ) permission = Permission.objects.get(name='Can change core user') staff_user.user_permissions.add(permission) staff_user.is_staff = True diff --git a/buildly/tests/test_loadinitialdata.py b/buildly/tests/test_loadinitialdata.py index 4c4ccc66..b326b1cc 100644 --- a/buildly/tests/test_loadinitialdata.py +++ b/buildly/tests/test_loadinitialdata.py @@ -45,18 +45,31 @@ def test_without_default_organization(self): opts = {} call_command('loadinitialdata', *args, **opts) - assert CoreGroup.objects.filter(name='Global Admin', is_global=True, permissions=15).count() == 1 + assert ( + CoreGroup.objects.filter( + name='Global Admin', is_global=True, permissions=15 + ).count() + == 1 + ) assert Organization.objects.all().count() == 0 assert CoreUser.objects.filter(is_superuser=True).count() == 1 +<<<<<<< HEAD +======= +>>>>>>> master @override_settings(DEBUG=True) def test_create_user_debug_no_password(self): args = [] opts = {} call_command('loadinitialdata', *args, **opts) - assert CoreGroup.objects.filter(name='Global Admin', is_global=True, permissions=15).count() == 1 + assert ( + CoreGroup.objects.filter( + name='Global Admin', is_global=True, permissions=15 + ).count() + == 1 + ) assert Organization.objects.filter(name=settings.DEFAULT_ORG).count() == 1 assert CoreUser.objects.filter(is_superuser=True).count() == 1 @@ -66,6 +79,15 @@ def test_create_user_no_debug_no_password(self): opts = {} call_command('loadinitialdata', *args, **opts) - assert CoreGroup.objects.filter(name='Global Admin', is_global=True, permissions=15).count() == 1 + assert ( + CoreGroup.objects.filter( + name='Global Admin', is_global=True, permissions=15 + ).count() + == 1 + ) assert Organization.objects.filter(name=settings.DEFAULT_ORG).count() == 1 - assert CoreUser.objects.filter(is_superuser=True).count() == 0 \ No newline at end of file +<<<<<<< HEAD + assert CoreUser.objects.filter(is_superuser=True).count() == 0 +======= + assert CoreUser.objects.filter(is_superuser=True).count() == 0 +>>>>>>> master diff --git a/buildly/wsgi.py b/buildly/wsgi.py index e3a545c4..92eb60cc 100644 --- a/buildly/wsgi.py +++ b/buildly/wsgi.py @@ -11,7 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", - "buildly.settings.production") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "buildly.settings.production") application = get_wsgi_application() diff --git a/conftest.py b/conftest.py index f725c4e0..6e084174 100644 --- a/conftest.py +++ b/conftest.py @@ -11,7 +11,6 @@ def request_factory(): @pytest.fixture(scope='session') def wsgi_request_factory(): - def _make_wsgi_request(data: dict = None): environ = { 'REQUEST_METHOD': 'get', diff --git a/core/admin.py b/core/admin.py index 475cef87..51df420d 100644 --- a/core/admin.py +++ b/core/admin.py @@ -31,7 +31,7 @@ class OrganizationAdmin(admin.ModelAdmin): class CoreGroupAdmin(admin.ModelAdmin): list_display = ('id', 'name', 'organization', 'is_global', 'is_org_level', 'is_default', 'permissions') display = 'Core Group' - search_fields = ('name', 'organization__name', ) + search_fields = ('name', 'organization__name') class CoreUserAdmin(UserAdmin): @@ -46,7 +46,7 @@ class CoreUserAdmin(UserAdmin): (_('Preferences'), {'fields': ('email_preferences', 'push_preferences')}), (_('Important dates'), {'fields': ('last_login', 'date_joined', 'create_date', 'edit_date')}), ) - filter_horizontal = ('core_groups', 'user_permissions', ) + filter_horizontal = ('core_groups', 'user_permissions') def get_fieldsets(self, request, obj=None): @@ -58,7 +58,13 @@ def get_fieldsets(self, request, obj=None): if not request.user.is_superuser: fieldsets[2][1]['fields'] = ('is_active', 'is_staff') else: - fieldsets[2][1]['fields'] = ('is_active', 'is_staff', 'is_superuser', 'core_groups', 'user_permissions') + fieldsets[2][1]['fields'] = ( + 'is_active', + 'is_staff', + 'is_superuser', + 'core_groups', + 'user_permissions', + ) return fieldsets diff --git a/core/auth_pipeline.py b/core/auth_pipeline.py index 39ab6282..e68f1fff 100644 --- a/core/auth_pipeline.py +++ b/core/auth_pipeline.py @@ -18,17 +18,18 @@ def create_organization(core_user=None, *args, **kwargs): # create or get an organization and associate it to the core user if settings.DEFAULT_ORG: - organization, created = Organization.objects.get_or_create(name=settings.DEFAULT_ORG) + organization, created = Organization.objects.get_or_create( + name=settings.DEFAULT_ORG + ) else: - organization, created = Organization.objects.get_or_create(name=core_user.username) + organization, created = Organization.objects.get_or_create( + name=core_user.username + ) core_user.organization = organization core_user.save() - return { - 'is_new_org': created, - 'organization': organization - } + return {'is_new_org': created, 'organization': organization} def auth_allowed(backend, details, response, *args, **kwargs): @@ -60,28 +61,31 @@ def auth_allowed(backend, details, response, *args, **kwargs): else: domain = email.split('@', 1)[1] if whitelisted_emails or whitelisted_domains: - allowed = (email in whitelisted_emails or domain in - whitelisted_domains) + allowed = email in whitelisted_emails or domain in whitelisted_domains # Check if the user email domain matches with one of the org oauth # domains and add the organization uuid in the details if allowed: org_uuid = Organization.objects.values_list( - 'organization_uuid', flat=True).get(name=settings.DEFAULT_ORG) + 'organization_uuid', flat=True + ).get(name=settings.DEFAULT_ORG) details.update({'organization_uuid': org_uuid}) else: try: org_uuid = Organization.objects.values_list( - 'organization_uuid', flat=True).get( - oauth_domains__contains=[domain]) + 'organization_uuid', flat=True + ).get(oauth_domains__contains=[domain]) details.update({'organization_uuid': org_uuid}) allowed = True except Organization.DoesNotExist: pass except Organization.MultipleObjectsReturned as e: - logger.warning('There is more than one Organization with ' - 'the domain {}.\n{}'.format(domain, e)) + logger.warning( + 'There is more than one Organization with ' + 'the domain {}.\n{}'.format(domain, e) + ) if not allowed: - return render_to_response('unauthorized.html', - context={'STATIC_URL': static_url}) + return render_to_response( + 'unauthorized.html', context={'STATIC_URL': static_url} + ) diff --git a/core/email_utils.py b/core/email_utils.py index b9b7404c..3ad65397 100644 --- a/core/email_utils.py +++ b/core/email_utils.py @@ -3,14 +3,25 @@ from django.conf import settings -def send_email(email_address: str, subject: str, context: dict, template_name: str, - html_template_name: str = None) -> int: +def send_email( + email_address: str, + subject: str, + context: dict, + template_name: str, + html_template_name: str = None, +) -> int: text_content = loader.render_to_string(template_name, context, using=None) - html_content = loader.render_to_string(html_template_name, context, using=None) if html_template_name else None + html_content = ( + loader.render_to_string(html_template_name, context, using=None) + if html_template_name + else None + ) return send_email_body(email_address, subject, text_content, html_content) -def send_email_body(email_address: str, subject: str, text_content: str, html_content: str = None) -> int: +def send_email_body( + email_address: str, subject: str, text_content: str, html_content: str = None +) -> int: msg = EmailMultiAlternatives( from_email=settings.DEFAULT_FROM_EMAIL, subject=subject, diff --git a/core/jwt_utils.py b/core/jwt_utils.py index 64c430ef..499432f0 100644 --- a/core/jwt_utils.py +++ b/core/jwt_utils.py @@ -16,7 +16,8 @@ def payload_enricher(request): username = request.POST.get('username') try: user = CoreUser.objects.values( - 'core_user_uuid', 'organization__organization_uuid').get(username=username) + 'core_user_uuid', 'organization__organization_uuid' + ).get(username=username) except CoreUser.DoesNotExist: logger.error('No matching CoreUser found.') raise PermissionDenied('No matching CoreUser found.') @@ -26,7 +27,9 @@ def payload_enricher(request): } elif request.POST.get('refresh_token'): try: - refresh_token = RefreshToken.objects.get(token=request.POST.get('refresh_token')) + refresh_token = RefreshToken.objects.get( + token=request.POST.get('refresh_token') + ) user = refresh_token.user return { 'core_user_uuid': user.core_user_uuid, @@ -43,6 +46,6 @@ def create_invitation_token(email_address: str, organization: Organization): payload = { 'email': email_address, 'org_uuid': str(organization.organization_uuid) if organization else None, - 'exp': datetime.datetime.utcnow() + exp_hours + 'exp': datetime.datetime.utcnow() + exp_hours, } return jwt.encode(payload, settings.SECRET_KEY, algorithm='HS256').decode('utf-8') diff --git a/core/middleware.py b/core/middleware.py index e0604ea3..86251fe7 100644 --- a/core/middleware.py +++ b/core/middleware.py @@ -11,7 +11,6 @@ class DisableCsrfCheck(MiddlewareMixin): - def process_request(self, req): attr = '_dont_enforce_csrf_checks' if not getattr(req, attr, False): @@ -29,10 +28,10 @@ def process_request(self, req): class ExceptionMiddleware(MiddlewareMixin): - @staticmethod def process_exception(request, exception): if isinstance(exception, MIDDLEWARE_EXCEPTIONS): - return JsonResponse(data=json.loads(exception.content), - status=exception.status) + return JsonResponse( + data=json.loads(exception.content), status=exception.status + ) return None diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py index 5929e4c6..00b2f311 100644 --- a/core/migrations/0001_initial.py +++ b/core/migrations/0001_initial.py @@ -15,6 +15,11 @@ def migrate_email_alert(apps, schema_editor): for record in CoreUser.objects.all(): record.email_preferences = {'environmental': record.email_alert_flag} record.save() +<<<<<<< HEAD + + +======= +>>>>>>> master class Migration(migrations.Migration): dependencies = [ @@ -26,13 +31,324 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Industry', fields=[ +<<<<<<< HEAD + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'name', + models.CharField( + blank=True, + default='Tech', + max_length=255, + verbose_name='Industry Name', + ), + ), + ( + 'description', + models.TextField( + blank=True, + max_length=765, + null=True, + verbose_name='Description/Notes', + ), + ), + ('create_date', models.DateTimeField(blank=True, null=True)), + ('edit_date', models.DateTimeField(blank=True, null=True)), + ], + options={'verbose_name_plural': 'Industries', 'ordering': ('name',)}, + ), + migrations.CreateModel( + name='Organization', + fields=[ + ('organization_uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='Organization UUID')), + ('name', models.CharField(blank=True, help_text='Each end user must be grouped into an organization', max_length=255, verbose_name='Organization Name')), + ('description', models.TextField(blank=True, help_text='Description of organization', max_length=765, null=True, verbose_name='Description/Notes')), + ('organization_url', models.CharField(blank=True, help_text='Link to organizations external web site', max_length=255, null=True)), + ('create_date', models.DateTimeField(blank=True, null=True)), + ('edit_date', models.DateTimeField(blank=True, null=True)), + ( + 'oauth_domains', + django.contrib.postgres.fields.ArrayField( + base_field=models.CharField( + blank=True, + max_length=255, + null=True, + verbose_name='OAuth Domains', + ), + blank=True, + null=True, + size=None, + ), + ), + ( + 'date_format', + models.CharField( + blank=True, + default='DD.MM.YYYY', + max_length=50, + verbose_name='Date Format', + ), + ), + ('phone', models.CharField(blank=True, max_length=20, null=True)), + ('industries', models.ManyToManyField(blank=True, help_text='Type of Industry the organization belongs to if any', related_name='organizations', to='core.Industry')), + ('allow_import_export', models.BooleanField(default=False, verbose_name='To allow import export functionality')), + ('radius', models.FloatField(blank=True, max_length=20, null=True)), + ], + options={'verbose_name_plural': 'Organizations', 'ordering': ('name',)}, + ), + migrations.CreateModel( + name='CoreSites', + fields=[ + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ('name', models.CharField(blank=True, max_length=255, null=True)), + ('privacy_disclaimer', models.TextField(blank=True, null=True)), + ('created', models.DateTimeField(blank=True, null=True)), + ('updated', models.DateTimeField(blank=True, null=True)), + ( + 'whitelisted_domains', + models.TextField( + blank=True, null=True, verbose_name='Whitelisted Domains' + ), + ), + ( + 'site', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to='sites.Site' + ), + ), + ], + options={'verbose_name': 'Core Site', 'verbose_name_plural': 'Core Sites'}, + ), + migrations.CreateModel( + name='CoreGroup', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.CharField(default=uuid.uuid4, max_length=255, unique=True, verbose_name='CoreGroup UUID')), + ('name', models.CharField(max_length=80, verbose_name='Name of the role')), + ('is_global', models.BooleanField(default=False, verbose_name='Is global group')), + ('is_org_level', models.BooleanField(default=False, verbose_name='Is organization level group')), + ('is_default', models.BooleanField(default=False, verbose_name='Is organization default group')), + ('permissions', models.PositiveSmallIntegerField(default=4, help_text='Decimal integer from 0 to 15 converted from 4-bit binary, each bit indicates permissions for CRUD', verbose_name='Permissions')), + ('create_date', models.DateTimeField(default=django.utils.timezone.now)), + ('edit_date', models.DateTimeField(blank=True, null=True)), + ('organization', models.ForeignKey(blank=True, help_text='Related Org to associate with', null=True, on_delete=django.db.models.deletion.CASCADE, to='core.Organization')), + ], + options={ + 'ordering': ('name',), + }, + ), + migrations.CreateModel( + name='EmailTemplate', + fields=[ + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ('subject', models.CharField(max_length=255, verbose_name='Subject')), + ( + 'type', + models.PositiveSmallIntegerField( + choices=[(1, 'Password resetting'), (2, 'Invitation')], + verbose_name='Type of template', + ), + ), + ( + 'template', + models.TextField( + blank=True, + null=True, + verbose_name='Reset password e-mail template (text)', + ), + ), + ( + 'template_html', + models.TextField( + blank=True, + null=True, + verbose_name='Reset password e-mail template (HTML)', + ), + ), + ( + 'organization', + models.ForeignKey( + help_text='Related Org to associate with', + on_delete=django.db.models.deletion.CASCADE, + to='core.Organization', + verbose_name='Organization', + ), + ), + ], + options={ + 'verbose_name': 'Email Template', + 'verbose_name_plural': 'Email Templates', + 'unique_together': {('organization', 'type')}, + }, + ), + migrations.CreateModel( + name='LogicModule', + fields=[ + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'module_uuid', + models.CharField( + default=uuid.uuid4, + max_length=255, + unique=True, + verbose_name='Logic Module UUID', + ), + ), + ( + 'name', + models.CharField( + blank=True, max_length=255, verbose_name='Logic Module Name' + ), + ), + ( + 'description', + models.TextField( + blank=True, + max_length=765, + null=True, + verbose_name='Description/Notes', + ), + ), + ('endpoint', models.CharField(blank=True, max_length=255, null=True)), + ('endpoint_name', models.CharField(blank=True, max_length=255, null=True)), + ('api_specification', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True)), + ('docs_endpoint', models.CharField(blank=True, max_length=255, null=True)), + ('core_groups', models.ManyToManyField(blank=True, related_name='logic_module_set', related_query_name='logic_module', to='core.CoreGroup', verbose_name='Logic Module groups')), +======= ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(blank=True, default='Tech', max_length=255, verbose_name='Industry Name')), ('description', models.TextField(blank=True, max_length=765, null=True, verbose_name='Description/Notes')), +>>>>>>> master + ('create_date', models.DateTimeField(blank=True, null=True)), + ('edit_date', models.DateTimeField(blank=True, null=True)), + ], + options={ +<<<<<<< HEAD + 'verbose_name_plural': 'Logic Modules', + 'ordering': ('name',), + 'unique_together': {('endpoint', 'endpoint_name')}, + }, + ), + migrations.CreateModel( + name='CoreUser', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('core_user_uuid', models.CharField(default=uuid.uuid4, max_length=255, unique=True, verbose_name='CoreUser UUID')), + ('title', models.CharField(blank=True, choices=[('mr', 'Mr.'), ('mrs', 'Mrs.'), ('ms', 'Ms.')], max_length=3, null=True)), + ('contact_info', models.CharField(blank=True, max_length=255, null=True)), + ('privacy_disclaimer_accepted', models.BooleanField(default=False)), + ('create_date', models.DateTimeField(default=django.utils.timezone.now)), + ('edit_date', models.DateTimeField(blank=True, null=True)), + ('core_groups', models.ManyToManyField(blank=True, related_name='user_set', related_query_name='user', to='core.CoreGroup', verbose_name='User groups')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), + ('organization', models.ForeignKey(blank=True, help_text='Related Org to associate with', null=True, on_delete=django.db.models.deletion.CASCADE, to='core.Organization')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ('email_alert_flag', models.BooleanField(blank=True, default=False, null=True)), + ('email_preferences', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True)), + ('push_preferences', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True)), + ('user_timezone', models.CharField(blank=True, max_length=255, null=True)), + ], + options={ + 'ordering': ('first_name',), + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='OrganizationType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(blank=True, help_text='Organization type', max_length=255, verbose_name='Name')), ('create_date', models.DateTimeField(blank=True, null=True)), ('edit_date', models.DateTimeField(blank=True, null=True)), ], options={ + 'verbose_name_plural': 'Organization Types', + 'ordering': ('name',), + }, + ), + migrations.AddField( + model_name='organization', + name='organization_type', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='core.OrganizationType'), + ), + migrations.CreateModel( + name='Consortium', + fields=[ + ('consortium_uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='Consortium UUID')), + ('name', models.CharField(blank=True, help_text='Multiple organizations form a consortium together', max_length=255, verbose_name='Consortium Name')), + ('create_date', models.DateTimeField(default=django.utils.timezone.now)), + ('edit_date', models.DateTimeField(blank=True, null=True)), + ('custodian_uuids', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Custodian UUIDs'), blank=True, null=True, size=None)), + ], + options={ + 'verbose_name_plural': 'Consortiums', + 'ordering': ('name',), + }, + ), + migrations.RunPython(migrate_email_alert, + migrations.RunPython.noop, + ), + migrations.RemoveField( + model_name='coreuser', + name='email_alert_flag', + ), + migrations.RemoveField( + model_name='consortium', + name='custodian_uuids', + ), + migrations.AddField( + model_name='consortium', + name='organization_uuids', + field=django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(blank=True, null=True, verbose_name='Organization UUIDs'), blank=True, null=True, size=None), + ), + migrations.AlterField( + model_name='organization', + name='radius', + field=models.FloatField(blank=True, default=0.0, max_length=20, null=True), +======= 'verbose_name_plural': 'Industries', 'ordering': ('name',), }, @@ -128,6 +444,7 @@ class Migration(migrations.Migration): 'ordering': ('name',), 'unique_together': {('endpoint', 'endpoint_name')}, }, +>>>>>>> master ), migrations.CreateModel( name='CoreUser', diff --git a/core/models.py b/core/models.py index ed7555c3..1e8cd935 100644 --- a/core/models.py +++ b/core/models.py @@ -61,7 +61,9 @@ def save(self, *args, **kwargs): class Industry(models.Model): name = models.CharField("Industry Name", max_length=255, blank=True, default="Tech") - description = models.TextField("Description/Notes", max_length=765, null=True, blank=True) + description = models.TextField( + "Description/Notes", max_length=765, null=True, blank=True + ) create_date = models.DateTimeField(null=True, blank=True) edit_date = models.DateTimeField(null=True, blank=True) @@ -106,20 +108,82 @@ def save(self, *args, **kwargs): def __str__(self): return str(self.name) +class OrganizationType(models.Model): + """ + Allows organization to be of multiple types. + Supported types are: + 1. Logistics Provider + 2. Packer + 3. Producer + 4. Receiver + 5. Shipper + 6. Warehouse + """ + + name = models.CharField( + "Name", max_length=255, blank=True, help_text="Organization type" + ) + create_date = models.DateTimeField(null=True, blank=True) + edit_date = models.DateTimeField(null=True, blank=True) + + class Meta: + ordering = ('name',) + verbose_name_plural = "Organization Types" + + def save(self, *args, **kwargs): + if self.create_date is None: + self.create_date = timezone.now() + self.edit_date = timezone.now() + super(OrganizationType, self).save(*args, **kwargs) + + def __str__(self): + return str(self.name) + + class Organization(models.Model): """ The organization instance. There could be multiple organizations inside one application. When organization is created two CoreGroups are created automatically: Admins group and default Users group. """ - organization_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, verbose_name='Organization UUID') - name = models.CharField("Organization Name", max_length=255, blank=True, help_text="Each end user must be grouped into an organization") - description = models.TextField("Description/Notes", max_length=765, null=True, blank=True, help_text="Description of organization") - organization_url = models.CharField(blank=True, null=True, max_length=255, help_text="Link to organizations external web site") - industries = models.ManyToManyField(Industry, blank=True, related_name='organizations', help_text="Type of Industry the organization belongs to if any") + + organization_uuid = models.UUIDField( + primary_key=True, default=uuid.uuid4, verbose_name='Organization UUID' + ) + name = models.CharField( + "Organization Name", + max_length=255, + blank=True, + help_text="Each end user must be grouped into an organization", + ) + description = models.TextField( + "Description/Notes", + max_length=765, + null=True, + blank=True, + help_text="Description of organization", + ) + organization_url = models.CharField( + blank=True, + null=True, + max_length=255, + help_text="Link to organizations external web site", + ) + industries = models.ManyToManyField( + Industry, + blank=True, + related_name='organizations', + help_text="Type of Industry the organization belongs to if any", + ) create_date = models.DateTimeField(null=True, blank=True) edit_date = models.DateTimeField(null=True, blank=True) - oauth_domains = ArrayField(models.CharField("OAuth Domains", max_length=255, null=True, blank=True), null=True, blank=True) - date_format = models.CharField("Date Format", max_length=50, blank=True, default="DD.MM.YYYY") + oauth_domains = ArrayField( + models.CharField("OAuth Domains", max_length=255, null=True, blank=True), + null=True, + blank=True, + ) + date_format = models.CharField( + "Date Format", max_length=50, blank=True, default="DD.MM.YYYY" + ) phone = models.CharField(max_length=20, blank=True, null=True) allow_import_export = models.BooleanField('To allow import export functionality', default=False) radius = models.FloatField(max_length=20, blank=True, null=True, default = 0.0) @@ -148,7 +212,7 @@ def _create_initial_groups(self): organization=self, is_org_level=True, name='Admins', - permissions=PERMISSIONS_ORG_ADMIN + permissions=PERMISSIONS_ORG_ADMIN, ) CoreGroup.objects.create( @@ -156,7 +220,7 @@ def _create_initial_groups(self): is_org_level=True, is_default=True, name='Users', - permissions=PERMISSIONS_VIEW_ONLY + permissions=PERMISSIONS_VIEW_ONLY, ) @@ -167,13 +231,26 @@ class CoreGroup(models.Model): Permissions field is the decimal integer from 0 to 15 converted from 4-bit binary, each bit indicates permissions for CRUD. For example: 12 -> 1100 -> CR__ (allowed to Create and Read). """ - uuid = models.CharField('CoreGroup UUID', max_length=255, default=uuid.uuid4, unique=True) + + uuid = models.CharField( + 'CoreGroup UUID', max_length=255, default=uuid.uuid4, unique=True + ) name = models.CharField('Name of the role', max_length=80) - organization = models.ForeignKey(Organization, blank=True, null=True, on_delete=models.CASCADE, help_text='Related Org to associate with') + organization = models.ForeignKey( + Organization, + blank=True, + null=True, + on_delete=models.CASCADE, + help_text='Related Org to associate with', + ) is_global = models.BooleanField('Is global group', default=False) is_org_level = models.BooleanField('Is organization level group', default=False) is_default = models.BooleanField('Is organization default group', default=False) - permissions = models.PositiveSmallIntegerField('Permissions', default=PERMISSIONS_VIEW_ONLY, help_text='Decimal integer from 0 to 15 converted from 4-bit binary, each bit indicates permissions for CRUD') + permissions = models.PositiveSmallIntegerField( + 'Permissions', + default=PERMISSIONS_VIEW_ONLY, + help_text='Decimal integer from 0 to 15 converted from 4-bit binary, each bit indicates permissions for CRUD', + ) create_date = models.DateTimeField(default=timezone.now) edit_date = models.DateTimeField(null=True, blank=True) @@ -196,10 +273,15 @@ class CoreUser(AbstractUser): """ CoreUser is the registered user who belongs to some organization and can manage its projects. """ - TITLE_CHOICES = ( - ('mr', 'Mr.'), - ('mrs', 'Mrs.'), - ('ms', 'Ms.'), + + TITLE_CHOICES = (('mr', 'Mr.'), ('mrs', 'Mrs.'), ('ms', 'Ms.')) + + core_user_uuid = models.CharField( + max_length=255, verbose_name='CoreUser UUID', default=uuid.uuid4, unique=True + ) + USER_TYPE_CHOICES = ( + ('Developer', 'Developer'), + ('Product Team', 'Product Team'), ) USER_TYPE_CHOICES = ( @@ -210,8 +292,20 @@ class CoreUser(AbstractUser): core_user_uuid = models.CharField(max_length=255, verbose_name='CoreUser UUID', default=uuid.uuid4, unique=True) title = models.CharField(blank=True, null=True, max_length=3, choices=TITLE_CHOICES) contact_info = models.CharField(blank=True, null=True, max_length=255) - organization = models.ForeignKey(Organization, blank=True, null=True, on_delete=models.CASCADE, help_text='Related Org to associate with') - core_groups = models.ManyToManyField(CoreGroup, verbose_name='User groups', blank=True, related_name='user_set', related_query_name='user') + organization = models.ForeignKey( + Organization, + blank=True, + null=True, + on_delete=models.CASCADE, + help_text='Related Org to associate with', + ) + core_groups = models.ManyToManyField( + CoreGroup, + verbose_name='User groups', + blank=True, + related_name='user_set', + related_query_name='user', + ) privacy_disclaimer_accepted = models.BooleanField(default=False) create_date = models.DateTimeField(default=timezone.now) edit_date = models.DateTimeField(null=True, blank=True) @@ -237,7 +331,11 @@ def save(self, *args, **kwargs): super(CoreUser, self).save() if is_new: # Add default groups - self.core_groups.add(*CoreGroup.objects.filter(organization=self.organization, is_default=True)) + self.core_groups.add( + *CoreGroup.objects.filter( + organization=self.organization, is_default=True + ) + ) @property def is_org_admin(self) -> bool: @@ -245,7 +343,9 @@ def is_org_admin(self) -> bool: Check if user has organization level admin permissions """ if not hasattr(self, '_is_org_admin'): - self._is_org_admin = self.core_groups.filter(permissions=PERMISSIONS_ORG_ADMIN, is_org_level=True).exists() + self._is_org_admin = self.core_groups.filter( + permissions=PERMISSIONS_ORG_ADMIN, is_org_level=True + ).exists() return self._is_org_admin @property @@ -256,7 +356,9 @@ def is_global_admin(self) -> bool: if self.is_superuser: return True if not hasattr(self, '_is_global_admin'): - self._is_global_admin = self.core_groups.filter(permissions=PERMISSIONS_ADMIN, is_global=True).exists() + self._is_global_admin = self.core_groups.filter( + permissions=PERMISSIONS_ADMIN, is_global=True + ).exists() return self._is_global_admin @@ -264,11 +366,21 @@ class EmailTemplate(models.Model): """ Stores e-mail templates specific to organization """ - organization = models.ForeignKey(Organization, on_delete=models.CASCADE, verbose_name='Organization', help_text='Related Org to associate with') + + organization = models.ForeignKey( + Organization, + on_delete=models.CASCADE, + verbose_name='Organization', + help_text='Related Org to associate with', + ) subject = models.CharField('Subject', max_length=255) type = models.PositiveSmallIntegerField('Type of template', choices=TEMPLATE_TYPES) - template = models.TextField("Reset password e-mail template (text)", null=True, blank=True) - template_html = models.TextField("Reset password e-mail template (HTML)", null=True, blank=True) + template = models.TextField( + "Reset password e-mail template (text)", null=True, blank=True + ) + template_html = models.TextField( + "Reset password e-mail template (HTML)", null=True, blank=True + ) class Meta: unique_together = ('organization', 'type',) @@ -280,14 +392,27 @@ def __str__(self): class LogicModule(models.Model): - module_uuid = models.CharField(max_length=255, verbose_name='Logic Module UUID', default=uuid.uuid4, unique=True) + module_uuid = models.CharField( + max_length=255, + verbose_name='Logic Module UUID', + default=uuid.uuid4, + unique=True, + ) name = models.CharField("Logic Module Name", max_length=255, blank=True) - description = models.TextField("Description/Notes", max_length=765, null=True, blank=True) + description = models.TextField( + "Description/Notes", max_length=765, null=True, blank=True + ) endpoint = models.CharField(blank=True, null=True, max_length=255) endpoint_name = models.CharField(blank=True, null=True, max_length=255) docs_endpoint = models.CharField(blank=True, null=True, max_length=255) api_specification = JSONField(blank=True, null=True) - core_groups = models.ManyToManyField(CoreGroup, verbose_name='Logic Module groups', blank=True, related_name='logic_module_set', related_query_name='logic_module') + core_groups = models.ManyToManyField( + CoreGroup, + verbose_name='Logic Module groups', + blank=True, + related_name='logic_module_set', + related_query_name='logic_module', + ) create_date = models.DateTimeField(null=True, blank=True) edit_date = models.DateTimeField(null=True, blank=True) diff --git a/core/permissions.py b/core/permissions.py index f703d999..ab720ffa 100644 --- a/core/permissions.py +++ b/core/permissions.py @@ -10,7 +10,9 @@ def merge_permissions(permissions1: str, permissions2: str) -> str: """ Merge two CRUD permissions string representations""" - return ''.join(map(str, [max(int(i), int(j)) for i, j in zip(permissions1, permissions2)])) + return ''.join( + map(str, [max(int(i), int(j)) for i, j in zip(permissions1, permissions2)]) + ) def has_permission(permissions_: str, method: str) -> bool: @@ -24,7 +26,6 @@ def has_permission(permissions_: str, method: str) -> bool: 'PATCH': 2, 'DELETE': 3, 'OPTIONS': 1, - # CRUD actions 'create': 0, 'list': 1, @@ -42,7 +43,6 @@ def has_permission(permissions_: str, method: str) -> bool: class IsSuperUserBrowseableAPI(permissions.BasePermission): - def has_permission(self, request, view): if request.user.is_authenticated: if view.__class__.__name__ == 'SchemaView': @@ -95,7 +95,9 @@ def has_permission(self, request, view): user_org = request.user.organization_id if 'organization' in request.data: - org_serializer = view.get_serializer_class()().get_fields()['organization'] + org_serializer = view.get_serializer_class()().get_fields()[ + 'organization' + ] primitive_value = request.data.get('organization') org = org_serializer.run_validation(primitive_value) return org.pk == user_org diff --git a/core/serializers.py b/core/serializers.py index 8a7d962c..c5d4d821 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -38,6 +38,7 @@ class PermissionsField(serializers.DictField): For example: 9 -> '1001' (binary representation) -> `{'create': True, 'read': False, 'update': False, 'delete': True}` """ + _keys = ('create', 'read', 'update', 'delete') def __init__(self, *args, **kwargs): @@ -52,35 +53,48 @@ def to_internal_value(self, data): data = super().to_internal_value(data) keys = data.keys() if not set(keys) == set(self._keys): - raise serializers.ValidationError("Permissions field: incorrect keys format") + raise serializers.ValidationError( + "Permissions field: incorrect keys format" + ) permissions = ''.join([str(int(data[key])) for key in self._keys]) return int(permissions, 2) class UUIDPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): - def to_representation(self, value): return str(super().to_representation(value)) class CoreGroupSerializer(serializers.ModelSerializer): permissions = PermissionsField(required=False) - organization = UUIDPrimaryKeyRelatedField(required=False, - queryset=Organization.objects.all(), - help_text="Related Org to associate with") + organization = UUIDPrimaryKeyRelatedField( + required=False, + queryset=Organization.objects.all(), + help_text="Related Org to associate with", + ) class Meta: model = CoreGroup read_only_fields = ('uuid', 'workflowlevel1s', 'workflowlevel2s') - fields = ('id', 'uuid', 'name', 'is_global', 'is_org_level', 'permissions', 'organization', 'workflowlevel1s', - 'workflowlevel2s') + fields = ( + 'id', + 'uuid', + 'name', + 'is_global', + 'is_org_level', + 'permissions', + 'organization', + 'workflowlevel1s', + 'workflowlevel2s', + ) class CoreUserSerializer(serializers.ModelSerializer): """ Default CoreUser serializer """ + is_active = serializers.BooleanField(required=False) core_groups = CoreGroupSerializer(read_only=True, many=True) invitation_token = serializers.CharField(required=False) @@ -111,6 +125,7 @@ class CoreUserWritableSerializer(CoreUserSerializer): """ Override default CoreUser serializer for writable actions (create, update, partial_update) """ + password = serializers.CharField(write_only=True) organization_name = serializers.CharField(source='organization.name') core_groups = serializers.PrimaryKeyRelatedField(many=True, queryset=CoreGroup.objects.all(), required=False) @@ -128,7 +143,9 @@ def create(self, validated_data): organization = validated_data.pop('organization') except (KeyError): organization = {'name': settings.DEFAULT_ORG} - organization, is_new_org = Organization.objects.get_or_create(**organization) + + org_name = organization['name'] + organization, is_new_org = Organization.objects.get_or_create(name=str(org_name).lower()) core_groups = validated_data.pop('core_groups', []) product = validated_data.pop('product', None) @@ -154,9 +171,9 @@ def create(self, validated_data): template_name = 'email/coreuser/approval.txt' html_template_name = 'email/coreuser/approval.html' context = { - 'approval_link': approval_link, - 'coreuser_name': coreuser.first_name + ' ' + coreuser.last_name, - 'organization_name': organization + 'approval_link': approval_link, + 'coreuser_name': coreuser.first_name + ' ' + coreuser.last_name, + 'organization_name': organization, } if is_new_org: admin = CoreUser.objects.filter(is_superuser=True) # Global Admin @@ -205,7 +222,6 @@ class CoreUserProfileSerializer(serializers.Serializer): contact_info = serializers.CharField(required=False) password = serializers.CharField(required=False) organization_name = serializers.CharField(required=False) - user_type = serializers.CharField(required=False) survey_status = serializers.BooleanField(required=False) email_preferences = serializers.JSONField(required=False) @@ -252,17 +268,21 @@ def update(self, instance, validated_data): class CoreUserInvitationSerializer(serializers.Serializer): - emails = serializers.ListField(child=serializers.EmailField(), - min_length=1, max_length=10) + emails = serializers.ListField( + child=serializers.EmailField(), min_length=1, max_length=10 + ) class CoreUserResetPasswordSerializer(serializers.Serializer): """Serializer for reset password request data """ + email = serializers.EmailField() def save(self, **kwargs): - resetpass_url = urljoin(settings.FRONTEND_URL, settings.RESETPASS_CONFIRM_URL_PATH) + resetpass_url = urljoin( + settings.FRONTEND_URL, settings.RESETPASS_CONFIRM_URL_PATH + ) resetpass_url = resetpass_url + '{uid}/{token}/' email = self.validated_data["email"] @@ -277,14 +297,22 @@ def save(self, **kwargs): } # get specific subj and templates for user's organization - tpl = EmailTemplate.objects.filter(organization=user.organization, type=TEMPLATE_RESET_PASSWORD).first() + tpl = EmailTemplate.objects.filter( + organization=user.organization, type=TEMPLATE_RESET_PASSWORD + ).first() if not tpl: - tpl = EmailTemplate.objects.filter(organization__name=settings.DEFAULT_ORG, - type=TEMPLATE_RESET_PASSWORD).first() + tpl = EmailTemplate.objects.filter( + organization__name=settings.DEFAULT_ORG, + type=TEMPLATE_RESET_PASSWORD, + ).first() if tpl and tpl.template: context = Context(context) text_content = Template(tpl.template).render(context) - html_content = Template(tpl.template_html).render(context) if tpl.template_html else None + html_content = ( + Template(tpl.template_html).render(context) + if tpl.template_html + else None + ) count += send_email_body(email, tpl.subject, text_content, html_content) continue @@ -292,7 +320,9 @@ def save(self, **kwargs): subject = 'Reset your password' template_name = 'email/coreuser/password_reset.txt' html_template_name = 'email/coreuser/password_reset.html' - count += send_email(email, subject, context, template_name, html_template_name) + count += send_email( + email, subject, context, template_name, html_template_name + ) return count @@ -300,6 +330,7 @@ def save(self, **kwargs): class CoreUserResetPasswordCheckSerializer(serializers.Serializer): """Serializer for checking token for resetting password """ + uid = serializers.CharField() token = serializers.CharField() @@ -321,6 +352,7 @@ def validate(self, attrs): class CoreUserResetPasswordConfirmSerializer(CoreUserResetPasswordCheckSerializer): """Serializer for reset password data """ + new_password1 = serializers.CharField(max_length=128) new_password2 = serializers.CharField(max_length=128) @@ -372,8 +404,15 @@ class ApplicationSerializer(serializers.ModelSerializer): class Meta: model = Application - fields = ('id', 'authorization_grant_type', 'client_id', 'client_secret', 'client_type', 'name', - 'redirect_uris') + fields = ( + 'id', + 'authorization_grant_type', + 'client_id', + 'client_secret', + 'client_type', + 'name', + 'redirect_uris', + ) def create(self, validated_data): validated_data['client_id'] = secrets.token_urlsafe(75) diff --git a/core/swagger.py b/core/swagger.py index d3a07859..c7a4b664 100644 --- a/core/swagger.py +++ b/core/swagger.py @@ -3,43 +3,43 @@ TOKEN_QUERY_PARAM = Parameter('token', IN_QUERY, type='string', required=True) -DETAIL_RESPONSE = {200: Schema( - type='object', - properties={ - 'detail': Schema(type='string') - }) +DETAIL_RESPONSE = { + 200: Schema(type='object', properties={'detail': Schema(type='string')}) } -SUCCESS_RESPONSE = {200: Schema( - type='object', - properties={ - 'success': Schema(type='boolean') - }) +SUCCESS_RESPONSE = { + 200: Schema(type='object', properties={'success': Schema(type='boolean')}) } -COREUSER_INVITE_RESPONSE = {200: Schema( - type='object', - properties={ - 'detail': Schema(type='string'), - 'invitations': Schema(type='array', items=Schema(type='string')) - }) +COREUSER_INVITE_RESPONSE = { + 200: Schema( + type='object', + properties={ + 'detail': Schema(type='string'), + 'invitations': Schema(type='array', items=Schema(type='string')), + }, + ) } -COREUSER_INVITE_CHECK_RESPONSE = {200: Schema( - type='object', - properties={ - 'email': Schema(type='string'), - 'organization': Schema(type='object', properties={ - 'organization_uuid': Schema(type='string'), - 'name': Schema(type='string'), - }) - }) +COREUSER_INVITE_CHECK_RESPONSE = { + 200: Schema( + type='object', + properties={ + 'email': Schema(type='string'), + 'organization': Schema( + type='object', + properties={ + 'organization_uuid': Schema(type='string'), + 'name': Schema(type='string'), + }, + ), + }, + ) } -COREUSER_RESETPASS_RESPONSE = {200: Schema( - type='object', - properties={ - 'detail': Schema(type='string'), - 'count': Schema(type='number') - }) +COREUSER_RESETPASS_RESPONSE = { + 200: Schema( + type='object', + properties={'detail': Schema(type='string'), 'count': Schema(type='number')}, + ) } diff --git a/core/tests/fixtures.py b/core/tests/fixtures.py index aded5e9d..bf2b3bce 100644 --- a/core/tests/fixtures.py +++ b/core/tests/fixtures.py @@ -17,7 +17,7 @@ 'password': '123qwe', 'organization_uuid': uuid.uuid4(), # 'organization': settings.DEFAULT_ORG, # Tweaked this to support organization name from front end - 'organization_name': settings.DEFAULT_ORG + 'organization_name': settings.DEFAULT_ORG, } @@ -46,8 +46,12 @@ def core_group(org): @pytest.fixture def org_admin(org): - group_org_admin = factories.CoreGroup(name='Org Admin', organization=org, is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN) + group_org_admin = factories.CoreGroup( + name='Org Admin', + organization=org, + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + ) coreuser = factories.CoreUser.create(organization=group_org_admin.organization) coreuser.core_groups.add(group_org_admin) return coreuser @@ -91,20 +95,38 @@ def oauth_refresh_token(): @pytest.fixture() def logic_module(): - return factories.LogicModule.create(name='documents', - endpoint_name='documents', - endpoint='http://documentservice:8080') + return factories.LogicModule.create( + name='documents', + endpoint_name='documents', + endpoint='http://documentservice:8080', + ) @pytest.fixture() def datamesh(): - lm1 = factories.LogicModule.create(name='location', endpoint_name='location', - endpoint='http://locationservice:8080') - lm2 = factories.LogicModule.create(name='documents', endpoint_name='documents', - endpoint='http://documentservice:8080') - lmm1 = factories.LogicModuleModel(logic_module_endpoint_name=lm1.endpoint_name, model='SiteProfile', - endpoint='/siteprofiles/', lookup_field_name='uuid') - lmm2 = factories.LogicModuleModel(logic_module_endpoint_name=lm2.endpoint_name, model='Document', - endpoint='/documents/', lookup_field_name='id') - relationship = factories.Relationship(origin_model=lmm1, related_model=lmm2, key='documents') + lm1 = factories.LogicModule.create( + name='location', + endpoint_name='location', + endpoint='http://locationservice:8080', + ) + lm2 = factories.LogicModule.create( + name='documents', + endpoint_name='documents', + endpoint='http://documentservice:8080', + ) + lmm1 = factories.LogicModuleModel( + logic_module_endpoint_name=lm1.endpoint_name, + model='SiteProfile', + endpoint='/siteprofiles/', + lookup_field_name='uuid', + ) + lmm2 = factories.LogicModuleModel( + logic_module_endpoint_name=lm2.endpoint_name, + model='Document', + endpoint='/documents/', + lookup_field_name='id', + ) + relationship = factories.Relationship( + origin_model=lmm1, related_model=lmm2, key='documents' + ) return lm1, lm2, relationship diff --git a/core/tests/test_accesstokenview.py b/core/tests/test_accesstokenview.py index 542b27e7..9ca557be 100644 --- a/core/tests/test_accesstokenview.py +++ b/core/tests/test_accesstokenview.py @@ -12,7 +12,9 @@ class TestAccessTokenListView: ENDPOINT_BASE_URL = reverse('accesstoken-list') - def test_list_accesstoken_superuser(self, auth_superuser_api_client, oauth_access_token, superuser): + def test_list_accesstoken_superuser( + self, auth_superuser_api_client, oauth_access_token, superuser + ): """ Superusers are able to list all the objects """ @@ -32,7 +34,9 @@ def test_list_accesstoken_normaluser(self, auth_api_client, oauth_access_token): class TestAccessTokenCreateView: ENDPOINT_BASE_URL = reverse('accesstoken-list') - def test_create_accesstoken_superuser(self, auth_superuser_api_client, oauth_application, superuser): + def test_create_accesstoken_superuser( + self, auth_superuser_api_client, oauth_application, superuser + ): """ Nobody is able to create new access tokens """ @@ -41,7 +45,7 @@ def test_create_accesstoken_superuser(self, auth_superuser_api_client, oauth_app 'user': superuser, 'application': oauth_application, 'expires': datetime.datetime.utcnow() + datetime.timedelta(hours=1), - 'scope': 'read write' + 'scope': 'read write', } response = auth_superuser_api_client.post(self.ENDPOINT_BASE_URL, data) @@ -52,13 +56,17 @@ def test_create_accesstoken_superuser(self, auth_superuser_api_client, oauth_app class TestAccessTokenRetrieveViews: ENDPOINT_BASE_URL = reverse('accesstoken-list') - def test_retrieve_unexisting_accesstoken(self, auth_superuser_api_client, superuser): + def test_retrieve_unexisting_accesstoken( + self, auth_superuser_api_client, superuser + ): url = f'{self.ENDPOINT_BASE_URL}1111/' response = auth_superuser_api_client.get(url) assert response.status_code == 404 - def test_retrieve_accesstoken_superuser(self, auth_superuser_api_client, oauth_access_token, superuser): + def test_retrieve_accesstoken_superuser( + self, auth_superuser_api_client, oauth_access_token, superuser + ): """ Superusers are able to retrieve any access token """ @@ -82,15 +90,15 @@ def test_retrieve_accesstoken_normaluser(self, auth_api_client, oauth_access_tok class TestAccessTokenUpdateView: ENDPOINT_BASE_URL = reverse('accesstoken-list') - def test_update_accesstoken_superuser(self, auth_superuser_api_client, oauth_access_token, superuser): + def test_update_accesstoken_superuser( + self, auth_superuser_api_client, oauth_access_token, superuser + ): """ Nobody is able to update access tokens """ url = f'{self.ENDPOINT_BASE_URL}{oauth_access_token.pk}/' - data = { - 'token': secrets.token_urlsafe(8) - } + data = {'token': secrets.token_urlsafe(8)} response = auth_superuser_api_client.put(url, data) assert response.status_code == 405 @@ -105,7 +113,9 @@ def test_delete_unexisting_accesstoken(self, auth_superuser_api_client, superuse response = auth_superuser_api_client.delete(url) assert response.status_code == 404 - def test_delete_accesstoken_superuser(self, auth_superuser_api_client, oauth_access_token, superuser): + def test_delete_accesstoken_superuser( + self, auth_superuser_api_client, oauth_access_token, superuser + ): """ Superusers are able to delete any access token """ @@ -131,7 +141,9 @@ def test_delete_accesstoken_normaluser(self, auth_api_client, oauth_access_token class TestAccessTokenFilterView: ENDPOINT_BASE_URL = reverse('accesstoken-list') - def test_filter_accesstoken_by_user_username(self, auth_superuser_api_client, oauth_access_token, superuser): + def test_filter_accesstoken_by_user_username( + self, auth_superuser_api_client, oauth_access_token, superuser + ): """ Superusers can filter access tokens by users' username """ diff --git a/core/tests/test_applicationview.py b/core/tests/test_applicationview.py index 99d33e96..bf9087f6 100644 --- a/core/tests/test_applicationview.py +++ b/core/tests/test_applicationview.py @@ -11,7 +11,9 @@ class TestApplicationListView: ENDPOINT_BASE_URL = reverse('application-list') - def test_list_application_superuser(self, auth_superuser_api_client, oauth_application, superuser): + def test_list_application_superuser( + self, auth_superuser_api_client, oauth_application, superuser + ): """ Superusers are able to list all the objects """ @@ -63,13 +65,17 @@ def test_create_application_normaluser(self, auth_api_client): class TestApplicationRetrieveViews: ENDPOINT_BASE_URL = reverse('application-list') - def test_retrieve_unexisting_application(self, auth_superuser_api_client, superuser): + def test_retrieve_unexisting_application( + self, auth_superuser_api_client, superuser + ): url = f'{self.ENDPOINT_BASE_URL}1111/' response = auth_superuser_api_client.get(url) assert response.status_code == 404 - def test_retrieve_application_superuser(self, auth_superuser_api_client, oauth_application, superuser): + def test_retrieve_application_superuser( + self, auth_superuser_api_client, oauth_application, superuser + ): """ Superusers are able to retrieve any oauth application """ @@ -93,7 +99,9 @@ def test_retrieve_application_normaluser(self, auth_api_client, oauth_applicatio class TestApplicationUpdateView: ENDPOINT_BASE_URL = reverse('application-list') - def test_update_application_superuser(self, auth_superuser_api_client, oauth_application, superuser): + def test_update_application_superuser( + self, auth_superuser_api_client, oauth_application, superuser + ): """ Superuser is able to update oauth applications """ @@ -102,7 +110,7 @@ def test_update_application_superuser(self, auth_superuser_api_client, oauth_app data = { 'client_type': Application.CLIENT_PUBLIC, 'authorization_grant_type': Application.GRANT_PASSWORD, - 'name': Faker('name').generate() + 'name': Faker('name').generate(), } response = auth_superuser_api_client.put(url, data) assert response.status_code == 200 @@ -117,7 +125,7 @@ def test_update_application_normaluser(self, auth_api_client, oauth_application) data = { 'client_type': Application.CLIENT_PUBLIC, 'authorization_grant_type': Application.GRANT_PASSWORD, - 'name': Faker('name').generate() + 'name': Faker('name').generate(), } response = auth_api_client.put(url, data) assert response.status_code == 403 @@ -133,7 +141,9 @@ def test_delete_unexisting_application(self, auth_superuser_api_client, superuse response = auth_superuser_api_client.delete(url) assert response.status_code == 404 - def test_delete_application_superuser(self, auth_superuser_api_client, oauth_application, superuser): + def test_delete_application_superuser( + self, auth_superuser_api_client, oauth_application, superuser + ): """ Superusers are able to delete any oauth application """ diff --git a/core/tests/test_auth_pipeline.py b/core/tests/test_auth_pipeline.py index 418c6791..2ed7869c 100644 --- a/core/tests/test_auth_pipeline.py +++ b/core/tests/test_auth_pipeline.py @@ -15,6 +15,7 @@ class OAuthTest(TestCase): """ Test cases for OAuth Provider interface """ + # Fake classes for testing class BackendTest(object): def __init__(self): @@ -32,7 +33,7 @@ def setUp(self): logging.disable(logging.WARNING) self.core_user = factories.CoreUser() self.org = factories.Organization(organization_uuid='12345') - self.app = factories.Application(user=self.core_user, ) + self.app = factories.Application(user=self.core_user) def tearDown(self): logging.disable(logging.NOTSET) @@ -48,10 +49,12 @@ def test_authorization_success(self): self.core_user.save() # Get Authorization token - basic_token = base64.b64encode(f'{self.app.client_id}:{self.app.client_secret}'.encode('utf-8')).decode('utf-8') + basic_token = base64.b64encode( + f'{self.app.client_id}:{self.app.client_secret}'.encode('utf-8') + ).decode('utf-8') headers = { 'HTTP_USER_AGENT': 'Test/1.0', - 'HTTP_AUTHORIZATION': f'Basic {basic_token}' + 'HTTP_AUTHORIZATION': f'Basic {basic_token}', } c = APIClient() @@ -61,7 +64,12 @@ def test_authorization_success(self): data = f'username={self.core_user.username}&password=1234&grant_type=password' - response = c.post(authorize_url, data=data, content_type='application/x-www-form-urlencoded', headers=headers) + response = c.post( + authorize_url, + data=data, + content_type='application/x-www-form-urlencoded', + headers=headers, + ) self.assertEqual(response.status_code, 200) self.assertIn('access_token', response.json()) self.assertIn('access_token_jwt', response.json()) @@ -71,9 +79,13 @@ def test_authorization_success(self): def test_create_organization_new_default_org(self): Organization.objects.filter(name=settings.DEFAULT_ORG).delete() - coreuser = factories.CoreUser(first_name='John', last_name='Lennon', organization=None) + coreuser = factories.CoreUser( + first_name='John', last_name='Lennon', organization=None + ) - response = auth_pipeline.create_organization(core_user=coreuser, is_new_core_user=True) + response = auth_pipeline.create_organization( + core_user=coreuser, is_new_core_user=True + ) self.assertIn('is_new_org', response) self.assertTrue(response['is_new_org']) @@ -88,7 +100,9 @@ def test_create_organization_new_default_org(self): def test_create_organization_new_username_org(self): coreuser = factories.CoreUser(first_name='John', last_name='Lennon') - response = auth_pipeline.create_organization(core_user=coreuser, is_new_core_user=True) + response = auth_pipeline.create_organization( + core_user=coreuser, is_new_core_user=True + ) self.assertIn('is_new_org', response) self.assertTrue(response['is_new_org']) @@ -106,7 +120,9 @@ def test_create_organization_org_exists(self): coreuser.organization = org coreuser.save() - response = auth_pipeline.create_organization(core_user=coreuser, is_new_core_user=True) + response = auth_pipeline.create_organization( + core_user=coreuser, is_new_core_user=True + ) self.assertIn('is_new_org', response) self.assertFalse(response['is_new_org']) @@ -120,11 +136,15 @@ def test_create_organization_org_exists(self): def test_create_organization_no_new_coreuser(self): coreuser = factories.CoreUser(first_name='John', last_name='Lennon') - response = auth_pipeline.create_organization(core_user=coreuser, is_new_core_user=False) + response = auth_pipeline.create_organization( + core_user=coreuser, is_new_core_user=False + ) self.assertIsNone(response) def test_create_organization_no_coreuser(self): - response = auth_pipeline.create_organization(core_user=None, is_new_core_user=True) + response = auth_pipeline.create_organization( + core_user=None, is_new_core_user=True + ) self.assertIsNone(response) def test_create_organization_no_is_new_core_user(self): @@ -139,10 +159,13 @@ def test_auth_allowed_not_in_whitelist(self): details = {'email': self.core_user.email} response = auth_pipeline.auth_allowed(backend, details, None) template_content = response.content - self.assertIn(b"You don't appear to have permissions to access " - b"the system.", template_content) - self.assertIn(b"Please check with your organization to have access.", - template_content) + self.assertIn( + b"You don't appear to have permissions to access " b"the system.", + template_content, + ) + self.assertIn( + b"Please check with your organization to have access.", template_content + ) def test_auth_allowed_in_whitelisted_domains_conf(self): factories.Organization(name=settings.DEFAULT_ORG) @@ -157,27 +180,32 @@ def test_auth_allowed_in_whitelisted_domains_conf(self): def test_auth_allowed_multi_oauth_domain(self): self.org.oauth_domains = ['testenv.com'] self.org.save() - factories.Organization(name='Another Org', - oauth_domains=['testenv.com']) + factories.Organization(name='Another Org', oauth_domains=['testenv.com']) backend = self.BackendTest() details = {'email': self.core_user.email} response = auth_pipeline.auth_allowed(backend, details, None) template_content = response.content - self.assertIn(b"You don't appear to have permissions to access " - b"the system.", template_content) - self.assertIn(b"Please check with your organization to have access.", - template_content) + self.assertIn( + b"You don't appear to have permissions to access " b"the system.", + template_content, + ) + self.assertIn( + b"Please check with your organization to have access.", template_content + ) def test_auth_allowed_no_whitelist_oauth_domain(self): backend = self.BackendTest() details = {'email': self.core_user.email} response = auth_pipeline.auth_allowed(backend, details, None) template_content = response.content - self.assertIn(b"You don't appear to have permissions to access " - b"the system.", template_content) - self.assertIn(b"Please check with your organization to have access.", - template_content) + self.assertIn( + b"You don't appear to have permissions to access " b"the system.", + template_content, + ) + self.assertIn( + b"Please check with your organization to have access.", template_content + ) def test_auth_allowed_no_email(self): factories.Organization(name=settings.DEFAULT_ORG) @@ -185,10 +213,13 @@ def test_auth_allowed_no_email(self): details = {} response = auth_pipeline.auth_allowed(backend, details, None) template_content = response.content - self.assertIn(b"You don't appear to have permissions to access " - b"the system.", template_content) - self.assertIn(b"Please check with your organization to have access.", - template_content) + self.assertIn( + b"You don't appear to have permissions to access " b"the system.", + template_content, + ) + self.assertIn( + b"Please check with your organization to have access.", template_content + ) def test_create_organization(self): pass diff --git a/core/tests/test_coregroupview.py b/core/tests/test_coregroupview.py index c5ef168b..95595e9f 100644 --- a/core/tests/test_coregroupview.py +++ b/core/tests/test_coregroupview.py @@ -9,7 +9,6 @@ @pytest.mark.django_db() class TestCoreGroupViewsPermissions: - def test_coregroup_views_permissions_unauth(self, request_factory): request = request_factory.get(reverse('coregroup-list')) response = CoreGroupViewSet.as_view({'get': 'list'})(request) @@ -58,13 +57,17 @@ def test_coregroup_views_permissions_org_member(self, request_factory, org_membe request = request_factory.patch(reverse('coregroup-detail', args=(1000,))) request.user = org_member - response = CoreGroupViewSet.as_view({'patch': 'partial_update'})(request, pk=1000) + response = CoreGroupViewSet.as_view({'patch': 'partial_update'})( + request, pk=1000 + ) assert response.status_code == 404 # it's allowed but wf1 is from different org request = request_factory.delete(reverse('coregroup-detail', args=(1000,))) request.user = org_member response = CoreGroupViewSet.as_view({'delete': 'destroy'})(request, pk=1000) - assert response.status_code == 404 # first checks if exists, then checks object permissions + assert ( + response.status_code == 404 + ) # first checks if exists, then checks object permissions def test_coregroup_views_permissions_org_admin(self, request_factory, org_admin): request = request_factory.get(reverse('coregroup-list')) @@ -72,8 +75,11 @@ def test_coregroup_views_permissions_org_admin(self, request_factory, org_admin) response = CoreGroupViewSet.as_view({'get': 'list'})(request) assert response.status_code == 200 - request = request_factory.post(reverse('coregroup-list'), {'organization': org_admin.organization.pk}, - format='json') + request = request_factory.post( + reverse('coregroup-list'), + {'organization': org_admin.organization.pk}, + format='json', + ) request.user = org_admin response = CoreGroupViewSet.as_view({'post': 'create'})(request) assert response.status_code == 400 @@ -101,10 +107,14 @@ def test_coregroup_views_permissions_org_admin(self, request_factory, org_admin) @pytest.mark.django_db() class TestCoreGroupCreateView: - - def test_coregroup_create_fail_missing_required_fields(self, request_factory, org_admin): - request = request_factory.post(reverse('coregroup-list'), {'organization': org_admin.organization.pk}, - format='json') + def test_coregroup_create_fail_missing_required_fields( + self, request_factory, org_admin + ): + request = request_factory.post( + reverse('coregroup-list'), + {'organization': org_admin.organization.pk}, + format='json', + ) request.user = org_admin response = CoreGroupViewSet.as_view({'post': 'create'})(request) assert response.status_code == 400 @@ -123,7 +133,7 @@ def test_coregroup_create_fail(self, request_factory, org_admin): data = { 'name': 'New Group', 'permissions': 9, - 'organization': org_admin.organization.pk + 'organization': org_admin.organization.pk, } request = request_factory.post(reverse('coregroup-list'), data, format='json') request.user = org_admin @@ -134,7 +144,7 @@ def test_coregroup_create_fail_again(self, request_factory, org_admin): data = { 'name': 'New Group', 'permissions': '1001', - 'organization': org_admin.organization.pk + 'organization': org_admin.organization.pk, } request = request_factory.post(reverse('coregroup-list'), data, format='json') request.user = org_admin @@ -144,8 +154,13 @@ def test_coregroup_create_fail_again(self, request_factory, org_admin): def test_coregroup_create(self, request_factory, org_admin): data = { 'name': 'New Group', - 'permissions': {'create': True, 'read': False, 'update': False, 'delete': True}, - 'organization': org_admin.organization.pk + 'permissions': { + 'create': True, + 'read': False, + 'update': False, + 'delete': True, + }, + 'organization': org_admin.organization.pk, } request = request_factory.post(reverse('coregroup-list'), data, format='json') request.user = org_admin @@ -160,7 +175,7 @@ def test_coregroup_create_int(self, request_factory, org_admin): data = { 'name': 'New Group', 'permissions': {'create': 1, 'read': 0, 'update': 0, 'delete': 1}, - 'organization': org_admin.organization.pk + 'organization': org_admin.organization.pk, } request = request_factory.post(reverse('coregroup-list'), data, format='json') request.user = org_admin @@ -173,29 +188,38 @@ def test_coregroup_create_int(self, request_factory, org_admin): @pytest.mark.django_db() class TestCoreGroupUpdateView: - def test_coregroup_update_fail(self, request_factory, org_admin): - coregroup = factories.CoreGroup.create(name='Program Admin', organization=org_admin.organization) + coregroup = factories.CoreGroup.create( + name='Program Admin', organization=org_admin.organization + ) - data = { - 'name': 'Admin of something else', - 'permissions': 9, - } + data = {'name': 'Admin of something else', 'permissions': 9} - request = request_factory.put(reverse('coregroup-detail', args=(coregroup.pk,)), data, format='json') + request = request_factory.put( + reverse('coregroup-detail', args=(coregroup.pk,)), data, format='json' + ) request.user = org_admin response = CoreGroupViewSet.as_view({'put': 'update'})(request, pk=coregroup.pk) assert response.status_code == 400 def test_coregroup_update(self, request_factory, org_admin): - coregroup = factories.CoreGroup.create(name='Program Admin', organization=org_admin.organization) + coregroup = factories.CoreGroup.create( + name='Program Admin', organization=org_admin.organization + ) data = { 'name': 'Admin of something else', - 'permissions': {'create': True, 'read': False, 'update': False, 'delete': True}, + 'permissions': { + 'create': True, + 'read': False, + 'update': False, + 'delete': True, + }, } - request = request_factory.put(reverse('coregroup-detail', args=(coregroup.pk,)), data, format='json') + request = request_factory.put( + reverse('coregroup-detail', args=(coregroup.pk,)), data, format='json' + ) request.user = org_admin response = CoreGroupViewSet.as_view({'put': 'update'})(request, pk=coregroup.pk) assert response.status_code == 200 @@ -206,7 +230,6 @@ def test_coregroup_update(self, request_factory, org_admin): @pytest.mark.django_db() class TestCoreGroupListView: - def test_coregroup_list(self, request_factory, org_admin): factories.CoreGroup.create(name='Group 1', organization=org_admin.organization) factories.CoreGroup.create(name='Group 2', organization=org_admin.organization) @@ -230,7 +253,9 @@ def test_coregroup_list_another_org(self, request_factory, org_admin): def test_coregroup_list_global(self, request_factory, org_admin): factories.CoreGroup.create(name='Group 1', organization=org_admin.organization) - factories.CoreGroup.create(name='Group Global', organization=None, is_global=True) + factories.CoreGroup.create( + name='Group Global', organization=None, is_global=True + ) request = request_factory.get(reverse('coregroup-list')) request.user = org_admin @@ -241,13 +266,14 @@ def test_coregroup_list_global(self, request_factory, org_admin): @pytest.mark.django_db() class TestCoreGroupDetailView: - def test_coregroup_detail(self, request_factory, org_admin): coregroup = factories.CoreGroup.create(organization=org_admin.organization) request = request_factory.get(reverse('coregroup-detail', args=(coregroup.pk,))) request.user = org_admin - response = CoreGroupViewSet.as_view({'get': 'retrieve'})(request, pk=coregroup.pk) + response = CoreGroupViewSet.as_view({'get': 'retrieve'})( + request, pk=coregroup.pk + ) assert response.status_code == 200 def test_coregroup_detail_another_org(self, request_factory, org_admin): @@ -256,19 +282,24 @@ def test_coregroup_detail_another_org(self, request_factory, org_admin): request = request_factory.get(reverse('coregroup-detail', args=(coregroup.pk,))) request.user = org_admin - response = CoreGroupViewSet.as_view({'get': 'retrieve'})(request, pk=coregroup.pk) + response = CoreGroupViewSet.as_view({'get': 'retrieve'})( + request, pk=coregroup.pk + ) assert response.status_code == 403 @pytest.mark.django_db() class TestCoreGroupDeleteView: - def test_coregroup_delete(self, request_factory, org_admin): coregroup = factories.CoreGroup.create(organization=org_admin.organization) - request = request_factory.delete(reverse('coregroup-detail', args=(coregroup.pk,))) + request = request_factory.delete( + reverse('coregroup-detail', args=(coregroup.pk,)) + ) request.user = org_admin - response = CoreGroupViewSet.as_view({'delete': 'destroy'})(request, pk=coregroup.pk) + response = CoreGroupViewSet.as_view({'delete': 'destroy'})( + request, pk=coregroup.pk + ) assert response.status_code == 204 with pytest.raises(CoreGroup.DoesNotExist): diff --git a/core/tests/test_coreuserview.py b/core/tests/test_coreuserview.py index 0d16a7b0..baf65153 100644 --- a/core/tests/test_coreuserview.py +++ b/core/tests/test_coreuserview.py @@ -51,8 +51,7 @@ def test_coreuser_views_permissions_unauth(request_factory): # has no permission request = request_factory.patch(reverse('coreuser-detail', args=(1,))) - response = CoreUserViewSet.as_view({'patch': 'partial_update'})(request, - pk=1) + response = CoreUserViewSet.as_view({'patch': 'partial_update'})(request, pk=1) assert response.status_code == 403 @@ -87,14 +86,12 @@ def test_coreuser_views_permissions_org_member(request_factory, org_member): # has no permission request = request_factory.patch(reverse('coreuser-detail', args=(1,))) request.user = org_member - response = CoreUserViewSet.as_view({'patch': 'partial_update'})(request, - pk=1) + response = CoreUserViewSet.as_view({'patch': 'partial_update'})(request, pk=1) assert response.status_code == 403 @pytest.mark.django_db() class TestCoreUserCreate: - def test_registration_fail(self, request_factory): # check that 'password' and 'organization name' fields are required for field_name in ['password', 'organization_name']: @@ -155,7 +152,9 @@ def test_registration_of_invited_org_user(self, request_factory, org_admin): def test_reused_token_invalidation(self, request_factory, org_admin): data = TEST_USER_DATA.copy() - registered_user = factories.CoreUser.create(is_active=False, email=data['email'], username='user_org') + registered_user = factories.CoreUser.create( + is_active=False, email=data['email'], username='user_org' + ) token = create_invitation_token(data['email'], org_admin.organization) data['invitation_token'] = token @@ -174,7 +173,9 @@ def test_email_mismatch_token_invalidation(self, request_factory, org_admin): def test_registration_with_core_groups(self, request_factory, org_admin): data = TEST_USER_DATA.copy() - groups = factories.CoreGroup.create_batch(2, organization=org_admin.organization) + groups = factories.CoreGroup.create_batch( + 2, organization=org_admin.organization + ) data['core_groups'] = [item.pk for item in groups] request = request_factory.post(reverse('coreuser-list'), data) response = CoreUserViewSet.as_view({'post': 'create'})(request) @@ -187,14 +188,13 @@ def test_registration_with_core_groups(self, request_factory, org_admin): @pytest.mark.django_db() class TestCoreUserUpdate: - def test_coreuser_update(self, request_factory, org_admin): - user = factories.CoreUser.create(is_active=False, organization=org_admin.organization, username='org_user') + user = factories.CoreUser.create( + is_active=False, organization=org_admin.organization, username='org_user' + ) pk = user.pk - data = { - 'is_active': True, - } + data = {'is_active': True} request = request_factory.patch(reverse('coreuser-detail', args=(pk,)), data) request.user = org_admin response = CoreUserViewSet.as_view({'patch': 'partial_update'})(request, pk=pk) @@ -204,27 +204,29 @@ def test_coreuser_update(self, request_factory, org_admin): def test_coreuser_update_dif_org(self, request_factory, org_admin): dif_org = factories.Organization(name='Another Org') - user = factories.CoreUser.create(is_active=False, organization=dif_org, username='another_org_user') + user = factories.CoreUser.create( + is_active=False, organization=dif_org, username='another_org_user' + ) pk = user.pk - data = { - 'is_active': True, - } + data = {'is_active': True} request = request_factory.patch(reverse('coreuser-detail', args=(pk,)), data) request.user = org_admin response = CoreUserViewSet.as_view({'patch': 'partial_update'})(request, pk=pk) assert response.status_code == 403 def test_coreuser_update_groups(self, request_factory, org_admin): - user = factories.CoreUser.create(is_active=False, organization=org_admin.organization, username='user_org') - initial_groups = factories.CoreGroup.create_batch(2, organization=user.organization) + user = factories.CoreUser.create( + is_active=False, organization=org_admin.organization, username='user_org' + ) + initial_groups = factories.CoreGroup.create_batch( + 2, organization=user.organization + ) user.core_groups.add(*initial_groups) pk = user.pk new_groups = factories.CoreGroup.create_batch(2, organization=user.organization) - data = { - 'core_groups': [item.pk for item in new_groups], - } + data = {'core_groups': [item.pk for item in new_groups]} request = request_factory.patch(reverse('coreuser-detail', args=(pk,)), data) request.user = org_admin response = CoreUserViewSet.as_view({'patch': 'partial_update'})(request, pk=pk) @@ -247,7 +249,6 @@ def test_coreuser_profile_update(self, request_factory, org_admin): @pytest.mark.django_db() class TestCoreUserInvite: - def test_invitation(self, request_factory, org_admin): data = {'emails': [TEST_USER_DATA['email']]} request = request_factory.post(reverse('coreuser-invite'), data) @@ -258,28 +259,43 @@ def test_invitation(self, request_factory, org_admin): def test_invitation_check(self, request_factory, org): token = create_invitation_token(TEST_USER_DATA['email'], org) - request = request_factory.get(reverse('coreuser-invite-check'), {'token': token}) + request = request_factory.get( + reverse('coreuser-invite-check'), {'token': token} + ) response = CoreUserViewSet.as_view({'get': 'invite_check'})(request) assert response.status_code == 200 assert response.data['email'] == TEST_USER_DATA['email'] - assert response.data['organization']['organization_uuid'] == org.organization_uuid + assert ( + response.data['organization']['organization_uuid'] == org.organization_uuid + ) def test_prevent_token_reuse(self, request_factory, org): token = create_invitation_token(TEST_USER_DATA['email'], org) - registered_user = factories.CoreUser.create(is_active=False, email=TEST_USER_DATA['email'], username='user_org') - request = request_factory.get(reverse('coreuser-invite-check'), {'token': token}) + registered_user = factories.CoreUser.create( + is_active=False, email=TEST_USER_DATA['email'], username='user_org' + ) + request = request_factory.get( + reverse('coreuser-invite-check'), {'token': token} + ) response = CoreUserViewSet.as_view({'get': 'invite_check'})(request) assert response.status_code == 401 @pytest.mark.django_db() class TestResetPassword(object): - - def test_reset_password_using_default_emailtemplate(self, request_factory, org_member): + def test_reset_password_using_default_emailtemplate( + self, request_factory, org_member + ): email = org_member.email assert list(org_member.organization.emailtemplate_set.all()) == [] # assert list(Organization.objects.filter(name=settings.DEFAULT_ORG)) == [] -- Removed this assertion to support organization name here +<<<<<<< HEAD + request = request_factory.post( + reverse('coreuser-reset-password'), {'email': email} + ) +======= request = request_factory.post(reverse('coreuser-reset-password'), {'email': email}) +>>>>>>> master response = CoreUserViewSet.as_view({'post': 'reset_password'})(request) assert response.status_code == 200 assert response.data['count'] == 1 @@ -288,7 +304,9 @@ def test_reset_password_using_default_emailtemplate(self, request_factory, org_m message = mail.outbox[0] assert message.to == [email] - resetpass_url = urljoin(settings.FRONTEND_URL, settings.RESETPASS_CONFIRM_URL_PATH) + resetpass_url = urljoin( + settings.FRONTEND_URL, settings.RESETPASS_CONFIRM_URL_PATH + ) uid = urlsafe_base64_encode(force_bytes(org_member.pk)) token = default_token_generator.make_token(org_member) assert f'{resetpass_url}{uid}/{token}/' in message.body @@ -304,10 +322,12 @@ def test_reset_password_using_org_template(self, request_factory, org_member): template=""" Custom template {{ password_reset_link }} - """ + """, ) - request = request_factory.post(reverse('coreuser-reset-password'), {'email': email}) + request = request_factory.post( + reverse('coreuser-reset-password'), {'email': email} + ) response = CoreUserViewSet.as_view({'post': 'reset_password'})(request) assert response.status_code == 200 assert response.data['count'] == 1 @@ -316,14 +336,18 @@ def test_reset_password_using_org_template(self, request_factory, org_member): message = mail.outbox[0] assert message.to == [email] - resetpass_url = urljoin(settings.FRONTEND_URL, settings.RESETPASS_CONFIRM_URL_PATH) + resetpass_url = urljoin( + settings.FRONTEND_URL, settings.RESETPASS_CONFIRM_URL_PATH + ) uid = urlsafe_base64_encode(force_bytes(org_member.pk)) token = default_token_generator.make_token(org_member) assert message.subject == 'Custom subject' assert 'Custom template' in message.body assert f'{resetpass_url}{uid}/{token}/' in message.body - def test_reset_password_using_default_org_template(self, request_factory, org_member): + def test_reset_password_using_default_org_template( + self, request_factory, org_member + ): email = org_member.email default_organization = factories.Organization(name=settings.DEFAULT_ORG) @@ -335,10 +359,12 @@ def test_reset_password_using_default_org_template(self, request_factory, org_me template=""" Custom template {{ password_reset_link }} - """ + """, ) - request = request_factory.post(reverse('coreuser-reset-password'), {'email': email}) + request = request_factory.post( + reverse('coreuser-reset-password'), {'email': email} + ) response = CoreUserViewSet.as_view({'post': 'reset_password'})(request) assert response.status_code == 200 assert response.data['count'] == 1 @@ -347,7 +373,9 @@ def test_reset_password_using_default_org_template(self, request_factory, org_me message = mail.outbox[0] assert message.to == [email] - resetpass_url = urljoin(settings.FRONTEND_URL, settings.RESETPASS_CONFIRM_URL_PATH) + resetpass_url = urljoin( + settings.FRONTEND_URL, settings.RESETPASS_CONFIRM_URL_PATH + ) uid = urlsafe_base64_encode(force_bytes(org_member.pk)) token = default_token_generator.make_token(org_member) assert message.subject == 'Custom subject' @@ -355,32 +383,39 @@ def test_reset_password_using_default_org_template(self, request_factory, org_me assert f'{resetpass_url}{uid}/{token}/' in message.body def test_reset_password_no_user(self, request_factory): - request = request_factory.post(reverse('coreuser-reset-password'), {'email': 'foo@example.com'}) + request = request_factory.post( + reverse('coreuser-reset-password'), {'email': 'foo@example.com'} + ) response = CoreUserViewSet.as_view({'post': 'reset_password'})(request) assert response.status_code == 200 assert response.data['count'] == 0 def test_reset_password_check(self, request_factory, reset_password_request): user, uid, token = reset_password_request - data = { - 'uid': uid, - 'token': token, - } + data = {'uid': uid, 'token': token} request = request_factory.post(reverse('coreuser-reset-password-check'), data) response = CoreUserViewSet.as_view({'post': 'reset_password_check'})(request) assert response.status_code == 200 assert response.data['success'] is True - def test_reset_password_check_expired(self, request_factory, reset_password_request): + def test_reset_password_check_expired( + self, request_factory, reset_password_request + ): user, uid, token = reset_password_request - data = { - 'uid': uid, - 'token': token, - } - mock_date = date.today() + timedelta(int(settings.PASSWORD_RESET_TIMEOUT_DAYS) + 1) - with mock.patch('django.contrib.auth.tokens.PasswordResetTokenGenerator._today', return_value=mock_date): - request = request_factory.post(reverse('coreuser-reset-password-check'), data) - response = CoreUserViewSet.as_view({'post': 'reset_password_check'})(request) + data = {'uid': uid, 'token': token} + mock_date = date.today() + timedelta( + int(settings.PASSWORD_RESET_TIMEOUT_DAYS) + 1 + ) + with mock.patch( + 'django.contrib.auth.tokens.PasswordResetTokenGenerator._today', + return_value=mock_date, + ): + request = request_factory.post( + reverse('coreuser-reset-password-check'), data + ) + response = CoreUserViewSet.as_view({'post': 'reset_password_check'})( + request + ) assert response.status_code == 200 assert response.data['success'] is False @@ -401,7 +436,9 @@ def test_reset_password_confirm(self, request_factory, reset_password_request): updated_user = CoreUser.objects.get(pk=user.pk) assert updated_user.check_password(test_password) - def test_reset_password_confirm_diff_passwords(self, request_factory, reset_password_request): + def test_reset_password_confirm_diff_passwords( + self, request_factory, reset_password_request + ): test_password1 = '5UU74e7nfU' test_password2 = '5UU74e7nfUa' user, uid, token = reset_password_request @@ -413,9 +450,13 @@ def test_reset_password_confirm_diff_passwords(self, request_factory, reset_pass } request = request_factory.post(reverse('coreuser-reset-password-confirm'), data) response = CoreUserViewSet.as_view({'post': 'reset_password_confirm'})(request) - assert response.status_code == 400 # validation error (password fields didn't match) + assert ( + response.status_code == 400 + ) # validation error (password fields didn't match) - def test_reset_password_confirm_token_expired(self, request_factory, reset_password_request): + def test_reset_password_confirm_token_expired( + self, request_factory, reset_password_request + ): test_password = '5UU74e7nfU' user, uid, token = reset_password_request data = { @@ -424,22 +465,59 @@ def test_reset_password_confirm_token_expired(self, request_factory, reset_passw 'uid': uid, 'token': token, } - mock_date = date.today() + timedelta(int(settings.PASSWORD_RESET_TIMEOUT_DAYS) + 1) - with mock.patch('django.contrib.auth.tokens.PasswordResetTokenGenerator._today', return_value=mock_date): - request = request_factory.post(reverse('coreuser-reset-password-confirm'), data) - response = CoreUserViewSet.as_view({'post': 'reset_password_confirm'})(request) - assert response.status_code == 400 # validation error (the token is expired) + mock_date = date.today() + timedelta( + int(settings.PASSWORD_RESET_TIMEOUT_DAYS) + 1 + ) + with mock.patch( + 'django.contrib.auth.tokens.PasswordResetTokenGenerator._today', + return_value=mock_date, + ): + request = request_factory.post( + reverse('coreuser-reset-password-confirm'), data + ) + response = CoreUserViewSet.as_view({'post': 'reset_password_confirm'})( + request + ) + assert ( + response.status_code == 400 + ) # validation error (the token is expired) @pytest.mark.django_db() class TestCoreUserRead(object): +<<<<<<< HEAD + keys = { + 'id', + 'core_user_uuid', + 'first_name', + 'last_name', + 'email', + 'username', + 'is_active', + 'title', + 'contact_info', + 'privacy_disclaimer_accepted', + 'organization', + 'core_groups', + 'email_preferences', + 'push_preferences', + 'user_timezone', + 'survey_status', + } + +======= keys = {'id', 'core_user_uuid', 'first_name', 'last_name', 'email', 'username', 'is_active', 'title', 'contact_info','privacy_disclaimer_accepted', 'organization', 'core_groups', 'email_preferences', 'push_preferences', 'user_timezone','user_type', 'survey_status'} +>>>>>>> master def test_coreuser_list(self, request_factory, org_member): - factories.CoreUser.create(organization=org_member.organization, username='another_user') # 2nd user of the org - factories.CoreUser.create(organization=factories.Organization(name='another otg'), - username='yet_another_user') # user of the different org + factories.CoreUser.create( + organization=org_member.organization, username='another_user' + ) # 2nd user of the org + factories.CoreUser.create( + organization=factories.Organization(name='another otg'), + username='yet_another_user', + ) # user of the different org request = request_factory.get(reverse('coreuser-list')) request.user = org_member response = CoreUserViewSet.as_view({'get': 'list'})(request) @@ -449,11 +527,15 @@ def test_coreuser_list(self, request_factory, org_member): assert set(data[0].keys()) == self.keys def test_coreuser_retrieve(self, request_factory, org_member): - core_user = factories.CoreUser.create(organization=org_member.organization, username='another_user') + core_user = factories.CoreUser.create( + organization=org_member.organization, username='another_user' + ) request = request_factory.get(reverse('coreuser-detail', args=(core_user.pk,))) request.user = org_member - response = CoreUserViewSet.as_view({'get': 'retrieve'})(request, pk=core_user.pk) + response = CoreUserViewSet.as_view({'get': 'retrieve'})( + request, pk=core_user.pk + ) assert response.status_code == 200 assert set(response.data.keys()) == self.keys diff --git a/core/tests/test_emailtemplates.py b/core/tests/test_emailtemplates.py index f10ff1a5..d6a3be50 100644 --- a/core/tests/test_emailtemplates.py +++ b/core/tests/test_emailtemplates.py @@ -7,7 +7,6 @@ @pytest.mark.django_db() class TestEmailTemplateModel: - def test_create(self, org): tpl = EmailTemplate( organization=org, @@ -16,18 +15,19 @@ def test_create(self, org): template=""" Custom template {{ password_reset_link }} - """ + """, ) tpl.save() - updated = EmailTemplate.objects.get(organization=org, type=TEMPLATE_RESET_PASSWORD) + updated = EmailTemplate.objects.get( + organization=org, type=TEMPLATE_RESET_PASSWORD + ) assert updated.subject == tpl.subject assert updated.template == tpl.template def test_create_fail_without_subj(self, org): tpl = EmailTemplate.objects.create( - organization=org, - type=TEMPLATE_RESET_PASSWORD + organization=org, type=TEMPLATE_RESET_PASSWORD ) with pytest.raises(ValidationError): tpl.full_clean() @@ -41,7 +41,7 @@ def test_create_fail_unique_constraint(self, org): template=""" Custom template {{ password_reset_link }} - """ + """, ) tpl.save() tpl = EmailTemplate( @@ -51,7 +51,7 @@ def test_create_fail_unique_constraint(self, org): template=""" Another custom template {{ password_reset_link }} - """ + """, ) with pytest.raises(IntegrityError): tpl.save() @@ -64,7 +64,7 @@ def test_update(self, org): template=""" Custom template {{ password_reset_link }} - """ + """, ) tpl.subject = 'Updated custom subject' @@ -74,6 +74,8 @@ def test_update(self, org): """ tpl.save() - updated = EmailTemplate.objects.get(organization=org, type=TEMPLATE_RESET_PASSWORD) + updated = EmailTemplate.objects.get( + organization=org, type=TEMPLATE_RESET_PASSWORD + ) assert updated.subject == tpl.subject assert updated.template == tpl.template diff --git a/core/tests/test_jwt_utils.py b/core/tests/test_jwt_utils.py index 4135b20c..0a86b104 100644 --- a/core/tests/test_jwt_utils.py +++ b/core/tests/test_jwt_utils.py @@ -5,7 +5,11 @@ from django.utils import timezone import factories -from oauth2_provider.models import get_application_model, get_access_token_model, get_refresh_token_model +from oauth2_provider.models import ( + get_application_model, + get_access_token_model, + get_refresh_token_model, +) from core.jwt_utils import payload_enricher from core.models import ROLE_ORGANIZATION_ADMIN @@ -17,7 +21,6 @@ class JWTUtilsTest(TestCase): - def setUp(self) -> None: self.rf = RequestFactory() self.core_user = factories.CoreUser() @@ -31,16 +34,17 @@ def setUp(self) -> None: ) application.save() access_token = AccessToken.objects.create( - user=self.core_user, token="1234567890", + user=self.core_user, + token="1234567890", application=application, expires=timezone.now() + datetime.timedelta(days=1), - scope="read write" + scope="read write", ) RefreshToken.objects.create( access_token=access_token, user=self.core_user, application=application, - token="007" + token="007", ) def test_jwt_payload_enricher(self): diff --git a/core/tests/test_logicmoduleview.py b/core/tests/test_logicmoduleview.py index c2984e07..685e2074 100644 --- a/core/tests/test_logicmoduleview.py +++ b/core/tests/test_logicmoduleview.py @@ -13,7 +13,6 @@ class LogicModuleViewsPermissionTest(TestCase): - def setUp(self): self.client = APIClient() self.core_user = factories.CoreUser() @@ -24,7 +23,7 @@ def setUp(self): 'id': 1, 'workflowlevel2_uuid': 1, 'name': 'test', - 'contact_uuid': 1 + 'contact_uuid': 1, } def make_logicmodule_request(self): @@ -50,8 +49,9 @@ def make_logicmodule_request_superuser(self): @pytest.mark.django_db() class TestLogicModuleUpdate: - - def test_logic_module_update_api_specification(self, request_factory, superuser, logic_module, monkeypatch): + def test_logic_module_update_api_specification( + self, request_factory, superuser, logic_module, monkeypatch + ): mocked_url = Mock(return_value='example.com') test_swagger = {'name': 'example'} mocked_swagger = Mock() @@ -60,7 +60,9 @@ def test_logic_module_update_api_specification(self, request_factory, superuser, monkeypatch.setattr(utils, 'get_swagger_url_by_logic_module', mocked_url) monkeypatch.setattr(utils, 'get_swagger_from_url', mocked_swagger) - request = request_factory.put(reverse('logicmodule-update-api-specification', args=(logic_module.pk,))) + request = request_factory.put( + reverse('logicmodule-update-api-specification', args=(logic_module.pk,)) + ) request.user = superuser view = LogicModuleViewSet.as_view({'put': 'update_api_specification'}) response = view(request, pk=logic_module.pk) diff --git a/core/tests/test_organizationview.py b/core/tests/test_organizationview.py index f5357d8f..a6e51aa1 100644 --- a/core/tests/test_organizationview.py +++ b/core/tests/test_organizationview.py @@ -34,4 +34,4 @@ def test_list_organization_names(self): view = OrganizationViewSet.as_view({'get': 'list'}) response = view(self.request) - self.assertEqual(response.status_code, 200) \ No newline at end of file + self.assertEqual(response.status_code, 200) diff --git a/core/tests/test_permissions.py b/core/tests/test_permissions.py index b8b27884..aa56f723 100644 --- a/core/tests/test_permissions.py +++ b/core/tests/test_permissions.py @@ -2,7 +2,6 @@ class TestMergePermissions: - def test_merge_permissions(self): """ Merge no access to view only permission will result in view only @@ -12,7 +11,6 @@ def test_merge_permissions(self): class TestHasPermission: - def test_has_permission_success(self): result = has_permission('0100', 'GET') assert result diff --git a/core/tests/test_refreshtokenview.py b/core/tests/test_refreshtokenview.py index 78d26526..3dca0251 100644 --- a/core/tests/test_refreshtokenview.py +++ b/core/tests/test_refreshtokenview.py @@ -3,15 +3,28 @@ from oauth2_provider.models import RefreshToken from rest_framework.reverse import reverse +<<<<<<< HEAD +from core.tests.fixtures import ( + auth_api_client, + auth_superuser_api_client, + oauth_application, + oauth_access_token, + oauth_refresh_token, + superuser, +) +======= from core.tests.fixtures import auth_api_client, auth_superuser_api_client, oauth_application, oauth_access_token, \ oauth_refresh_token, superuser +>>>>>>> master @pytest.mark.django_db() class TestRefreshTokenListView: ENDPOINT_BASE_URL = reverse('refreshtoken-list') - def test_list_refreshtoken_superuser(self, auth_superuser_api_client, oauth_refresh_token, superuser): + def test_list_refreshtoken_superuser( + self, auth_superuser_api_client, oauth_refresh_token, superuser + ): """ Superusers are able to list all the objects """ @@ -31,8 +44,13 @@ def test_list_refreshtoken_normaluser(self, auth_api_client, oauth_refresh_token class TestRefreshTokenCreateView: ENDPOINT_BASE_URL = reverse('refreshtoken-list') - def test_create_refreshtoken_superuser(self, auth_superuser_api_client, oauth_application, oauth_access_token, - superuser): + def test_create_refreshtoken_superuser( + self, + auth_superuser_api_client, + oauth_application, + oauth_access_token, + superuser, + ): """ Nobody is able to create new access tokens """ @@ -40,7 +58,7 @@ def test_create_refreshtoken_superuser(self, auth_superuser_api_client, oauth_ap 'token': secrets.token_urlsafe(8), 'user': superuser, 'application': oauth_application, - 'access_token': oauth_access_token + 'access_token': oauth_access_token, } response = auth_superuser_api_client.post(self.ENDPOINT_BASE_URL, data) @@ -51,13 +69,17 @@ def test_create_refreshtoken_superuser(self, auth_superuser_api_client, oauth_ap class TestRefreshTokenRetrieveViews: ENDPOINT_BASE_URL = reverse('refreshtoken-list') - def test_retrieve_unexisting_refreshtoken(self, auth_superuser_api_client, superuser): + def test_retrieve_unexisting_refreshtoken( + self, auth_superuser_api_client, superuser + ): url = f'{self.ENDPOINT_BASE_URL}1111/' response = auth_superuser_api_client.get(url) assert response.status_code == 404 - def test_retrieve_refreshtoken_superuser(self, auth_superuser_api_client, oauth_refresh_token, superuser): + def test_retrieve_refreshtoken_superuser( + self, auth_superuser_api_client, oauth_refresh_token, superuser + ): """ Superusers are able to retrieve any access token """ @@ -67,7 +89,9 @@ def test_retrieve_refreshtoken_superuser(self, auth_superuser_api_client, oauth_ assert response.status_code == 200 assert response.data['token'] == oauth_refresh_token.token - def test_retrieve_refreshtoken_normaluser(self, auth_api_client, oauth_refresh_token): + def test_retrieve_refreshtoken_normaluser( + self, auth_api_client, oauth_refresh_token + ): """ Normal users are not able to retrieve any access token """ @@ -81,15 +105,15 @@ def test_retrieve_refreshtoken_normaluser(self, auth_api_client, oauth_refresh_t class TestRefreshTokenUpdateView: ENDPOINT_BASE_URL = reverse('refreshtoken-list') - def test_update_refreshtoken_superuser(self, auth_superuser_api_client, oauth_refresh_token, superuser): + def test_update_refreshtoken_superuser( + self, auth_superuser_api_client, oauth_refresh_token, superuser + ): """ Nobody is able to update access tokens """ url = f'{self.ENDPOINT_BASE_URL}{oauth_refresh_token.pk}/' - data = { - 'token': secrets.token_urlsafe(8) - } + data = {'token': secrets.token_urlsafe(8)} response = auth_superuser_api_client.put(url, data) assert response.status_code == 405 @@ -104,7 +128,9 @@ def test_delete_unexisting_refreshtoken(self, auth_superuser_api_client, superus response = auth_superuser_api_client.delete(url) assert response.status_code == 404 - def test_delete_refreshtoken_superuser(self, auth_superuser_api_client, oauth_refresh_token, superuser): + def test_delete_refreshtoken_superuser( + self, auth_superuser_api_client, oauth_refresh_token, superuser + ): """ Superusers are able to delete any access token """ @@ -130,7 +156,9 @@ def test_delete_refreshtoken_normaluser(self, auth_api_client, oauth_refresh_tok class TestRefreshTokenFilterView: ENDPOINT_BASE_URL = reverse('refreshtoken-list') - def test_filter_refreshtoken_by_user_username(self, auth_superuser_api_client, oauth_refresh_token, superuser): + def test_filter_refreshtoken_by_user_username( + self, auth_superuser_api_client, oauth_refresh_token, superuser + ): """ Superusers can filter access tokens by users' username """ diff --git a/core/tests/test_serializers.py b/core/tests/test_serializers.py index 5bd1498d..7ff497cc 100644 --- a/core/tests/test_serializers.py +++ b/core/tests/test_serializers.py @@ -1,6 +1,10 @@ import pytest -from core.serializers import OrganizationSerializer, CoreGroupSerializer, CoreUserSerializer +from core.serializers import ( + OrganizationSerializer, + CoreGroupSerializer, + CoreUserSerializer, +) from core.tests.fixtures import core_group, org, org_member @pytest.mark.django_db() @@ -24,7 +28,7 @@ def test_org_serializer(request_factory, org): 'allow_import_export', 'radius', 'organization_type', - 'stripe_subscription_details' + 'stripe_subscription_details', ] assert set(data.keys()) == set(keys) @@ -34,16 +38,17 @@ def test_core_groups_serializer(request_factory, core_group): request = request_factory.get('') serializer = CoreGroupSerializer(core_group, context={'request': request}) data = serializer.data - keys = ['id', - 'uuid', - 'name', - 'is_global', - 'is_org_level', - 'permissions', - 'organization', - 'workflowlevel1s', - 'workflowlevel2s', - ] + keys = [ + 'id', + 'uuid', + 'name', + 'is_global', + 'is_org_level', + 'permissions', + 'organization', + 'workflowlevel1s', + 'workflowlevel2s', + ] assert set(data.keys()) == set(keys) assert isinstance(data['organization'], str) @@ -53,21 +58,24 @@ def test_core_user_serializer(request_factory, org_member): request = request_factory.get('') serializer = CoreUserSerializer(org_member, context={'request': request}) data = serializer.data - keys = ['id', - 'core_user_uuid', - 'first_name', - 'last_name', - 'email', - 'username', - 'is_active', - 'title', - 'contact_info', - 'privacy_disclaimer_accepted', - 'organization', - 'core_groups', - 'email_preferences', 'push_preferences', 'user_timezone', - 'user_type', - 'survey_status' - ] + keys = [ + 'id', + 'core_user_uuid', + 'first_name', + 'last_name', + 'email', + 'username', + 'is_active', + 'title', + 'contact_info', + 'privacy_disclaimer_accepted', + 'organization', + 'core_groups', + 'email_preferences', + 'push_preferences', + 'user_timezone', + 'user_type', + 'survey_status' + ] assert set(data.keys()) == set(keys) assert isinstance(data['organization'], dict) diff --git a/core/tests/test_utils.py b/core/tests/test_utils.py index 161b61ad..440e7786 100644 --- a/core/tests/test_utils.py +++ b/core/tests/test_utils.py @@ -25,19 +25,16 @@ def core_user(org): @pytest.fixture def application(org): return factories.Application.create( - client_id=settings.OAUTH_CLIENT_ID, - client_secret=settings.OAUTH_CLIENT_SECRET + client_id=settings.OAUTH_CLIENT_ID, client_secret=settings.OAUTH_CLIENT_SECRET ) + # ------------ Tests ------------------ class TestGenerateTokens(object): - @pytest.mark.django_db() - def test_success(self, wsgi_request_factory, core_user, - application, monkeypatch): - + def test_success(self, wsgi_request_factory, core_user, application, monkeypatch): def mock_create_token(*args, **kwargs): return { 'access_token': 'bZr9TVYykJnbVL1gAjq4Xhn3x1SY91', @@ -63,7 +60,7 @@ def mock_encode_jwt(*args, **kwargs): 'token_type': 'Bearer', 'scope': 'read write', 'refresh_token': 'UDJsQxZpjVxhuOgrCMLHnc79NI5ZpU', - 'access_token_jwt': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9' + 'access_token_jwt': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9', } # remove jwt enricher @@ -81,9 +78,9 @@ def mock_encode_jwt(*args, **kwargs): assert result == final_token @pytest.mark.django_db() - def test_success_without_mocking_token_creation(self, wsgi_request_factory, core_user, - application, monkeypatch): - + def test_success_without_mocking_token_creation( + self, wsgi_request_factory, core_user, application, monkeypatch + ): def mock_generate_payload(*args, **kwargs): return { 'iss': 'BuildlyTest', @@ -114,8 +111,9 @@ def mock_encode_jwt(*args, **kwargs): assert refresh_token.token == result['refresh_token'] @pytest.mark.django_db() - def test_without_encode_mock_success(self, wsgi_request_factory, core_user, - application, monkeypatch): + def test_without_encode_mock_success( + self, wsgi_request_factory, core_user, application, monkeypatch + ): def mock_create_token(*args, **kwargs): return { 'access_token': 'bZr9TVYykJnbVL1gAjq4Xhn3x1SY91', @@ -148,8 +146,9 @@ def mock_generate_payload(*args, **kwargs): utils.generate_access_tokens(request, core_user) @pytest.mark.django_db() - def test_no_func_mocks_success(self, wsgi_request_factory, core_user, - application, monkeypatch): + def test_no_func_mocks_success( + self, wsgi_request_factory, core_user, application, monkeypatch + ): # remove jwt enricher monkeypatch.delattr('django.conf.settings.JWT_PAYLOAD_ENRICHER') diff --git a/core/tests/test_views.py b/core/tests/test_views.py index 05024840..d24c826e 100644 --- a/core/tests/test_views.py +++ b/core/tests/test_views.py @@ -32,6 +32,7 @@ def core_user(org): # ------------ Tests ------------------ + class IndexViewTest(TestCase): def setUp(self): self.factory = RequestFactory() @@ -54,7 +55,6 @@ def test_health_check_success(self): class TestOAuthComplete(object): - def test_no_code_fail(self, client): oauth_url = reverse('oauth_complete', args=('github',)) expected_data = {'detail': 'Authorization code has to be provided.'} @@ -66,8 +66,9 @@ def test_no_code_fail(self, client): assert data == expected_data @pytest.mark.django_db() - def test_is_authenticated_success(self, wsgi_request_factory, core_user, monkeypatch): - + def test_is_authenticated_success( + self, wsgi_request_factory, core_user, monkeypatch + ): def mock_auth_complete(*args, **kwargs): return core_user @@ -77,15 +78,14 @@ def mock_auth_complete(*args, **kwargs): 'token_type': 'Bearer', 'scope': 'read write', 'refresh_token': 'UDJsQxZpjVxhuOgrCMLHnc79NI5ZpU', - 'access_token_jwt': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9' + 'access_token_jwt': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9', } # mock functions in order as in the code monkeypatch.setattr(web, 'user_is_authenticated', lambda x: True) monkeypatch.setattr(web, 'partial_pipeline_data', lambda x, y: None) monkeypatch.setattr(oauth.BaseOAuth2, 'auth_complete', mock_auth_complete) - monkeypatch.setattr(web, 'generate_access_tokens', - lambda x, y: tokens) + monkeypatch.setattr(web, 'generate_access_tokens', lambda x, y: tokens) # mock request object request = wsgi_request_factory() @@ -100,7 +100,6 @@ def mock_auth_complete(*args, **kwargs): @pytest.mark.django_db() def test_user_success(self, wsgi_request_factory, core_user, monkeypatch): - def mock_auth_complete(*args, **kwargs): return core_user @@ -110,7 +109,7 @@ def mock_auth_complete(*args, **kwargs): 'token_type': 'Bearer', 'scope': 'read write', 'refresh_token': 'UDJsQxZpjVxhuOgrCMLHnc79NI5ZpU', - 'access_token_jwt': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9' + 'access_token_jwt': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9', } # mock functions in order as in the code @@ -154,7 +153,6 @@ def mock_auth_complete(*args, **kwargs): assert response.url == settings.LOGIN_URL def test_no_auth_no_user(self, wsgi_request_factory, monkeypatch): - def mock_auth_complete(*args, **kwargs): return None @@ -199,7 +197,9 @@ def test_without_code_xfail(self, wsgi_request_factory): web.oauth_complete(request, 'github') @pytest.mark.xfail(raises=SocialAuthNotConfigured) - def test_no_social_auth_redirect_url_xfail(self, settings, wsgi_request_factory, monkeypatch): + def test_no_social_auth_redirect_url_xfail( + self, settings, wsgi_request_factory, monkeypatch + ): settings.SOCIAL_AUTH_LOGIN_REDIRECT_URLS['github'] = None # mock functions @@ -215,7 +215,9 @@ def test_no_social_auth_redirect_url_xfail(self, settings, wsgi_request_factory, web.oauth_complete(request, 'github') @pytest.mark.xfail(raises=SocialAuthNotConfigured) - def test_no_support_social_auth_backend_xfail(self, settings, wsgi_request_factory, monkeypatch): + def test_no_support_social_auth_backend_xfail( + self, settings, wsgi_request_factory, monkeypatch + ): del settings.SOCIAL_AUTH_LOGIN_REDIRECT_URLS['github'] # mock functions diff --git a/core/urls.py b/core/urls.py index df1af8ac..eb18b9c0 100644 --- a/core/urls.py +++ b/core/urls.py @@ -33,10 +33,13 @@ path('datamesh/', include('datamesh.urls')), path('', include('gateway.urls')), path('', include('workflow.urls')), - # Auth backend URL's - path('oauth/', include('oauth2_provider_jwt.urls', namespace='oauth2_provider_jwt')), - re_path(r'^oauth/complete/(?P[^/]+)/$', oauth_complete, name='oauth_complete'), + path( + 'oauth/', include('oauth2_provider_jwt.urls', namespace='oauth2_provider_jwt') + ), + re_path( + r'^oauth/complete/(?P[^/]+)/$', oauth_complete, name='oauth_complete' + ), ] urlpatterns += staticfiles_urlpatterns() + router.urls diff --git a/core/utils.py b/core/utils.py index 5a2661ba..b650020f 100644 --- a/core/utils.py +++ b/core/utils.py @@ -20,8 +20,7 @@ def generate_access_tokens(request: WSGIRequest, user: User): request.user = user request.grant_type = '' request.client = Application.objects.get( - client_id=settings.OAUTH_CLIENT_ID, - client_secret=settings.OAUTH_CLIENT_SECRET + client_id=settings.OAUTH_CLIENT_ID, client_secret=settings.OAUTH_CLIENT_SECRET ) token = bearer_token.create_token(request, refresh_token=True) bearer_token.request_validator.save_bearer_token(token, request) diff --git a/core/views/consortium.py b/core/views/consortium.py index 9ca2cc18..e6961597 100644 --- a/core/views/consortium.py +++ b/core/views/consortium.py @@ -49,7 +49,14 @@ def list(self, request): def get_permissions(self): try: # return permission_classes depending on `action` +<<<<<<< HEAD + return [ + permission() + for permission in self.permission_classes_by_action[self.action] + ] +======= return [permission() for permission in self.permission_classes_by_action[self.action]] +>>>>>>> master except KeyError: # action is not set return default permission_classes return [permission() for permission in self.permission_classes] diff --git a/core/views/coregroup.py b/core/views/coregroup.py index 59bc2738..fe33612d 100644 --- a/core/views/coregroup.py +++ b/core/views/coregroup.py @@ -12,6 +12,7 @@ class CoreGroupViewSet(viewsets.ModelViewSet): It's used for creating groups of Core Users inside an organization and defining model level permissions for this group """ + queryset = CoreGroup.objects.all() serializer_class = CoreGroupSerializer permission_classes = (IsOrgMember,) diff --git a/core/views/coreuser.py b/core/views/coreuser.py index f62801dc..688b9d1f 100644 --- a/core/views/coreuser.py +++ b/core/views/coreuser.py @@ -22,8 +22,14 @@ from rest_framework.permissions import AllowAny from core.permissions import AllowAuthenticatedRead, AllowOnlyOrgAdmin, IsOrgMember -from core.swagger import (COREUSER_INVITE_RESPONSE, COREUSER_INVITE_CHECK_RESPONSE, COREUSER_RESETPASS_RESPONSE, - DETAIL_RESPONSE, SUCCESS_RESPONSE, TOKEN_QUERY_PARAM) +from core.swagger import ( + COREUSER_INVITE_RESPONSE, + COREUSER_INVITE_CHECK_RESPONSE, + COREUSER_RESETPASS_RESPONSE, + DETAIL_RESPONSE, + SUCCESS_RESPONSE, + TOKEN_QUERY_PARAM, +) from core.jwt_utils import create_invitation_token from core.email_utils import send_email import logging @@ -33,9 +39,13 @@ logger = logging.getLogger(__name__) -class CoreUserViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, - mixins.CreateModelMixin, mixins.UpdateModelMixin, - viewsets.GenericViewSet): +class CoreUserViewSet( + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.CreateModelMixin, + mixins.UpdateModelMixin, + viewsets.GenericViewSet, +): """ A core user is an extension of the default User object. A core user is also the primary relationship for identity and access to a logged in user. They are associated with an organization, Group (for permission though WorkflowTeam) @@ -73,7 +83,11 @@ class CoreUserViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, 'alert': CoreUserEmailAlertSerializer, 'update_org': CoreUserUpdateOrganizationSerializer, 'notification': CoreUserEmailNotificationSerializer, +<<<<<<< HEAD + 'alert': CoreUserEmailAlertSerializer, +======= +>>>>>>> master } def list(self, request, *args, **kwargs): @@ -83,7 +97,8 @@ def list(self, request, *args, **kwargs): organization_id = request.user.organization_id queryset = queryset.filter(organization_id=organization_id) serializer = self.get_serializer( - instance=queryset, context={'request': request}, many=True) + instance=queryset, context={'request': request}, many=True + ) return Response(serializer.data) def retrieve(self, request, *args, **kwargs): @@ -101,9 +116,11 @@ def me(self, request, *args, **kwargs): serializer = self.get_serializer(instance=user, context={'request': request}) return Response(serializer.data) - @swagger_auto_schema(methods=['post'], - request_body=CoreUserInvitationSerializer, - responses=COREUSER_INVITE_RESPONSE) + @swagger_auto_schema( + methods=['post'], + request_body=CoreUserInvitationSerializer, + responses=COREUSER_INVITE_RESPONSE, + ) @action(methods=['POST'], detail=False) def invite(self, request, *args, **kwargs): """ @@ -118,15 +135,15 @@ def invite(self, request, *args, **kwargs): links = self.perform_invite(serializer) return Response( - { - 'detail': 'The invitations were sent successfully.', - 'invitations': links, - }, - status=status.HTTP_200_OK) + {'detail': 'The invitations were sent successfully.', 'invitations': links}, + status=status.HTTP_200_OK, + ) - @swagger_auto_schema(methods=['get'], - responses=COREUSER_INVITE_CHECK_RESPONSE, - manual_parameters=[TOKEN_QUERY_PARAM]) + @swagger_auto_schema( + methods=['get'], + responses=COREUSER_INVITE_CHECK_RESPONSE, + manual_parameters=[TOKEN_QUERY_PARAM], + ) @action(methods=['GET'], detail=False) def invite_check(self, request, *args, **kwargs): """ @@ -136,43 +153,57 @@ def invite_check(self, request, *args, **kwargs): try: token = self.request.query_params['token'] except KeyError: - return Response({'detail': 'No token is provided.'}, - status.HTTP_401_UNAUTHORIZED) + return Response( + {'detail': 'No token is provided.'}, status.HTTP_401_UNAUTHORIZED + ) try: - decoded = jwt.decode(token, settings.SECRET_KEY, - algorithms='HS256') + decoded = jwt.decode(token, settings.SECRET_KEY, algorithms='HS256') except jwt.DecodeError: - return Response({'detail': 'Token is not valid.'}, - status.HTTP_401_UNAUTHORIZED) + return Response( + {'detail': 'Token is not valid.'}, status.HTTP_401_UNAUTHORIZED + ) except jwt.ExpiredSignatureError: - return Response({'detail': 'Token is expired.'}, - status.HTTP_401_UNAUTHORIZED) + return Response( + {'detail': 'Token is expired.'}, status.HTTP_401_UNAUTHORIZED + ) if CoreUser.objects.filter(email=decoded['email']).exists(): - return Response({'detail': 'Token has been used.'}, - status.HTTP_401_UNAUTHORIZED) - + return Response( + {'detail': 'Token has been used.'}, status.HTTP_401_UNAUTHORIZED + ) + +<<<<<<< HEAD + organization = ( + Organization.objects.values('organization_uuid', 'name').get( + organization_uuid=decoded['org_uuid'] + ) + if decoded['org_uuid'] + else None + ) +======= organization = Organization.objects \ .values('organization_uuid', 'name') \ .get(organization_uuid=decoded['org_uuid']) \ if decoded['org_uuid'] else None +>>>>>>> master - return Response({ - 'email': decoded['email'], - 'organization': organization - }, status=status.HTTP_200_OK) + return Response( + {'email': decoded['email'], 'organization': organization}, + status=status.HTTP_200_OK, + ) @transaction.atomic def perform_invite(self, serializer): - reg_location = urljoin(settings.FRONTEND_URL, - settings.REGISTRATION_URL_PATH) + reg_location = urljoin(settings.FRONTEND_URL, settings.REGISTRATION_URL_PATH) reg_location = reg_location + '?token={}' email_addresses = serializer.validated_data.get('emails') user = self.request.user organization = user.organization - registered_emails = CoreUser.objects.filter(email__in=email_addresses).values_list('email', flat=True) + registered_emails = CoreUser.objects.filter( + email__in=email_addresses + ).values_list('email', flat=True) links = [] for email_address in email_addresses: @@ -190,21 +221,23 @@ def perform_invite(self, serializer): # create the used context for the E-mail templates context = { 'invitation_link': invitation_link, - 'org_admin_name': user.name - if hasattr(user, 'coreuser') else '', - 'organization_name': organization.name - if organization else '' + 'org_admin_name': user.name if hasattr(user, 'coreuser') else '', + 'organization_name': organization.name if organization else '', } subject = 'Application Access' # TODO we need to make this dynamic template_name = 'email/coreuser/invitation.txt' html_template_name = 'email/coreuser/invitation.html' - send_email(email_address, subject, context, template_name, html_template_name) + send_email( + email_address, subject, context, template_name, html_template_name + ) return links - @swagger_auto_schema(methods=['post'], - request_body=CoreUserResetPasswordSerializer, - responses=COREUSER_RESETPASS_RESPONSE) + @swagger_auto_schema( + methods=['post'], + request_body=CoreUserResetPasswordSerializer, + responses=COREUSER_RESETPASS_RESPONSE, + ) @action(methods=['POST'], detail=False) def reset_password(self, request, *args, **kwargs): """ @@ -220,26 +253,27 @@ def reset_password(self, request, *args, **kwargs): 'detail': 'The reset password link was sent successfully.', 'count': count, }, - status=status.HTTP_200_OK) + status=status.HTTP_200_OK, + ) - @swagger_auto_schema(methods=['post'], - request_body=CoreUserResetPasswordCheckSerializer, - responses=SUCCESS_RESPONSE) + @swagger_auto_schema( + methods=['post'], + request_body=CoreUserResetPasswordCheckSerializer, + responses=SUCCESS_RESPONSE, + ) @action(methods=['POST'], detail=False) def reset_password_check(self, request, *args, **kwargs): """ This endpoint is used to check that token is valid. """ serializer = self.get_serializer(data=request.data) - return Response( - { - 'success': serializer.is_valid(), - }, - status=status.HTTP_200_OK) + return Response({'success': serializer.is_valid()}, status=status.HTTP_200_OK) - @swagger_auto_schema(methods=['post'], - request_body=CoreUserResetPasswordConfirmSerializer, - responses=DETAIL_RESPONSE) + @swagger_auto_schema( + methods=['post'], + request_body=CoreUserResetPasswordConfirmSerializer, + responses=DETAIL_RESPONSE, + ) @action(methods=['POST'], detail=False) def reset_password_confirm(self, request, *args, **kwargs): """ @@ -249,10 +283,9 @@ def reset_password_confirm(self, request, *args, **kwargs): serializer.is_valid(raise_exception=True) serializer.save() return Response( - { - 'detail': 'The password was changed successfully.', - }, - status=status.HTTP_200_OK) + {'detail': 'The password was changed successfully.'}, + status=status.HTTP_200_OK, + ) def get_serializer_class(self): action_ = getattr(self, 'action', 'default') @@ -261,12 +294,23 @@ def get_serializer_class(self): def get_permissions(self): if hasattr(self, 'action'): # different permissions when creating a new user or resetting password +<<<<<<< HEAD + if self.action in [ + 'create', + 'reset_password', + 'reset_password_check', + 'reset_password_confirm', + 'invite_check', + 'update_profile', + ]: +======= if self.action in ['create', 'reset_password', 'reset_password_check', 'reset_password_confirm', 'invite_check', 'update_profile']: +>>>>>>> master return [permissions.AllowAny()] if self.action in ['update', 'partial_update', 'invite']: @@ -407,6 +451,104 @@ def notification(self, request, *args, **kwargs): return Response( { 'detail': 'The notification were sent successfully on email.', +<<<<<<< HEAD + }, status=status.HTTP_200_OK) + + @swagger_auto_schema( + methods=['post'], + request_body=CoreUserEmailAlertSerializer, + responses=SUCCESS_RESPONSE, + ) + @action(methods=['POST'], detail=False) + def alert(self, request, *args, **kwargs): + """ + a)Request alert message and uuid of organization + b)Access user uuids for that respective organization + c)Check if opted for email alert service + d)Send Email to the user's email with alert message + """ + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + org_uuid = request.data['organization_uuid'] + messages = request.data['messages'] + try: + for message in messages: + # try: + # time_tuple = datetime.strptime(message['date_time'], "%Y-%m-%dT%H:%M:%S%z") + # except ValueError: + # time_tuple = datetime.strptime(message['date_time'], "%Y-%m-%dT%H:%M:%S.%f%z") + # message['date_time'] = time_tuple.replace(tzinfo=tz.gettz('UTC')) + subject = '{} Alert'.format(message['parameter'].capitalize()) + if message.get('shipment_id'): + message['shipment_url'] = urljoin( + settings.FRONTEND_URL, + '/app/shipment/edit/:' + str(message['shipment_id']), + ) + else: + message['shipment_url'] = None + message['color'] = color_codes.get(message['severity']) + context = {'message': message} + template_name = 'email/coreuser/shipment_alert.txt' + html_template_name = 'email/coreuser/shipment_alert.html' + # TODO send email via preferences + core_users = CoreUser.objects.filter( + organization__organization_uuid=org_uuid + ) + for user in core_users: + email_address = user.email + preferences = user.email_preferences + if preferences and ( + preferences.get('environmental', None) + or preferences.get('geofence', None) + ): + # user_timezone = user.user_timezone + # if user_timezone: + # local_zone = tz.gettz(user_timezone) + # message['date_time'] = message['date_time'].astimezone(local_zone) + # else: + # message['date_time'] = time_tuple.strftime("%B %d, %Y, %I:%M %p")+" (UTC)" + send_email( + email_address, + subject, + context, + template_name, + html_template_name, + ) + except Exception as ex: + print('Exception: ', ex) + return Response( + {'detail': 'The alert messages were sent successfully on email.'}, + status=status.HTTP_200_OK, + ) + # This code is commented out as in future, It will need to impliment message service. + # for phone in phones: + # phone_number = phone + # account_sid = os.environ['TWILIO_ACCOUNT_SID'] + # auth_token = os.environ['TWILIO_AUTH_TOKEN'] + # client = Client(account_sid, auth_token) + # message = client.messages.create( + # body=alert_message, + # from_='+15082068927', + # to=phone_number + # ) + # print(message.sid) + + @action(detail=True, methods=['patch'], name='Update Profile') + def update_profile(self, request, pk=None, *args, **kwargs): + """ + Update a user Profile + """ + # the particular user in CoreUser table + user = self.get_object() + serializer = CoreUserProfileSerializer(user, data=request.data, partial=True) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data) + + +color_codes = {'error': '#cc3300', 'info': '#2196F3', 'success': '#339900'} +======= }, status=status.HTTP_200_OK ) +>>>>>>> master diff --git a/core/views/logicmodule.py b/core/views/logicmodule.py index a6df9fa0..3427d345 100644 --- a/core/views/logicmodule.py +++ b/core/views/logicmodule.py @@ -54,9 +54,7 @@ def update_api_specification(self, request, *args, **kwargs): response = utils.get_swagger_from_url(schema_url) spec_dict = response.json() - data = { - 'api_specification': spec_dict - } + data = {'api_specification': spec_dict} serializer = self.get_serializer(instance, data=data, partial=True) serializer.is_valid(raise_exception=True) diff --git a/core/views/oauth.py b/core/views/oauth.py index 7374df11..2d291828 100644 --- a/core/views/oauth.py +++ b/core/views/oauth.py @@ -2,13 +2,20 @@ import django_filters from oauth2_provider.models import AccessToken, Application, RefreshToken -from core.serializers import AccessTokenSerializer, ApplicationSerializer, RefreshTokenSerializer +from core.serializers import ( + AccessTokenSerializer, + ApplicationSerializer, + RefreshTokenSerializer, +) from core.permissions import IsSuperUser -class AccessTokenViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, - mixins.DestroyModelMixin, - viewsets.GenericViewSet): +class AccessTokenViewSet( + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet, +): """ title: Users' access tokens @@ -78,9 +85,12 @@ class ApplicationViewSet(viewsets.ModelViewSet): serializer_class = ApplicationSerializer -class RefreshTokenViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, - mixins.DestroyModelMixin, - viewsets.GenericViewSet): +class RefreshTokenViewSet( + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet, +): """ title: Users' refresh tokens diff --git a/core/views/organization.py b/core/views/organization.py index 32ce103d..5c54a5fc 100644 --- a/core/views/organization.py +++ b/core/views/organization.py @@ -10,7 +10,10 @@ from core.permissions import AllowOnlyOrgAdmin, IsOrgMember from django.views.decorators.csrf import csrf_exempt from rest_framework.permissions import AllowAny +<<<<<<< HEAD +======= +>>>>>>> master logger = logging.getLogger(__name__) @@ -52,8 +55,18 @@ def list(self, request, *args, **kwargs): serializer_class = OrganizationSerializer @csrf_exempt +<<<<<<< HEAD + @action( + detail=False, + methods=['get'], + permission_classes=[AllowAny], + name='Fetch Already existing Organization', + url_path='fetch_orgs', + ) +======= @action(detail=False, methods=['get'], permission_classes=[AllowAny], name='Fetch Already existing Organization', url_path='fetch_orgs') +>>>>>>> master def fetch_existing_orgs(self, request, pk=None, *args, **kwargs): """ Fetch Already existing Organizations in Buildly Core, @@ -102,4 +115,7 @@ class OrganizationTypeViewSet(viewsets.ModelViewSet): permission_classes = (AllowOnlyOrgAdmin,) queryset = OrganizationType.objects.all() serializer_class = OrganizationTypeSerializer +<<<<<<< HEAD +======= +>>>>>>> master diff --git a/core/views/web.py b/core/views/web.py index 3755754e..6b81b2a4 100644 --- a/core/views/web.py +++ b/core/views/web.py @@ -11,8 +11,12 @@ from rest_framework.reverse import reverse from social_core.exceptions import AuthFailed -from social_core.utils import (partial_pipeline_data, setting_url, - user_is_active, user_is_authenticated) +from social_core.utils import ( + partial_pipeline_data, + setting_url, + user_is_active, + user_is_authenticated, +) from social_django.utils import psa from core.exceptions import SocialAuthFailed, SocialAuthNotConfigured @@ -60,7 +64,9 @@ def oauth_complete(request, backend, *args, **kwargs): if backend not in settings.SOCIAL_AUTH_LOGIN_REDIRECT_URLS: raise SocialAuthNotConfigured(f'The backend {backend} is not supported.') elif not settings.SOCIAL_AUTH_LOGIN_REDIRECT_URLS.get(backend): - raise SocialAuthNotConfigured(f'A redirect URL for the backend {backend} was not defined.') + raise SocialAuthNotConfigured( + f'A redirect URL for the backend {backend} was not defined.' + ) # prepare request to validate code data = request.backend.strategy.request_data() @@ -86,7 +92,9 @@ def oauth_complete(request, backend, *args, **kwargs): tokens = generate_access_tokens(request, user) return JsonResponse(data=tokens, status=200) else: - url = setting_url(request.backend, 'INACTIVE_USER_URL', 'LOGIN_ERROR_URL', 'LOGIN_URL') + url = setting_url( + request.backend, 'INACTIVE_USER_URL', 'LOGIN_ERROR_URL', 'LOGIN_URL' + ) else: url = setting_url(request.backend, 'LOGIN_ERROR_URL', 'LOGIN_URL') diff --git a/datamesh/exceptions.py b/datamesh/exceptions.py index 03814915..749ab229 100644 --- a/datamesh/exceptions.py +++ b/datamesh/exceptions.py @@ -1,3 +1,2 @@ - class DatameshConfigurationError(BaseException): pass diff --git a/datamesh/filters.py b/datamesh/filters.py index 38d43773..35b28554 100644 --- a/datamesh/filters.py +++ b/datamesh/filters.py @@ -12,4 +12,9 @@ class JoinRecordFilter(django_filters.FilterSet): class Meta: model = JoinRecord - fields = ('record_id', 'record_uuid', 'related_record_id', 'related_record_uuid') + fields = ( + 'record_id', + 'record_uuid', + 'related_record_id', + 'related_record_uuid', + ) diff --git a/datamesh/management/commands/loadrelationships.py b/datamesh/management/commands/loadrelationships.py index 1901f446..2592b473 100644 --- a/datamesh/management/commands/loadrelationships.py +++ b/datamesh/management/commands/loadrelationships.py @@ -27,7 +27,7 @@ class Command(BaseCommand): def add_arguments(self, parser): """Add --file argument to Command.""" parser.add_argument( - '--file', default=None, nargs='?', help='Path of file to import.', + '--file', default=None, nargs='?', help='Path of file to import.' ) def handle(self, *args, **options): @@ -58,7 +58,7 @@ def handle(self, *args, **options): relationship, _ = Relationship.objects.get_or_create( origin_model=origin_model, related_model=related_model, - key='contact_siteprofile_relationship' + key='contact_siteprofile_relationship', ) eligible_join_records = [] # create JoinRecords with contact.id and siteprofile_uuid for all contacts @@ -78,11 +78,15 @@ def handle(self, *args, **options): relationship=relationship, record_uuid=contact['pk'], related_record_uuid=siteprofile_uuid, - defaults={'organization': organization} + defaults={'organization': organization}, ) print(join_record) eligible_join_records.append(join_record.pk) print(f'{self.counter} Contacts parsed and written to the JoinRecords.') # delete not eligible JoinRecords in this relationship - deleted, _ = JoinRecord.objects.exclude(pk__in=eligible_join_records).filter(relationship=relationship).delete() + deleted, _ = ( + JoinRecord.objects.exclude(pk__in=eligible_join_records) + .filter(relationship=relationship) + .delete() + ) print(f'{deleted} JoinRecord(s) deleted.') diff --git a/datamesh/managers.py b/datamesh/managers.py index 132734eb..6ae46a71 100644 --- a/datamesh/managers.py +++ b/datamesh/managers.py @@ -7,19 +7,20 @@ class LogicModuleModelManager(Manager): - def get_by_concatenated_model_name(self, concatenated_model_name: str) -> Model: - return self.annotate(swagger_model_name=Concat( - 'logic_module_endpoint_name', 'model')).filter( - swagger_model_name=concatenated_model_name).first() + return ( + self.annotate( + swagger_model_name=Concat('logic_module_endpoint_name', 'model') + ) + .filter(swagger_model_name=concatenated_model_name) + .first() + ) class JoinRecordManager(Manager): - - def get_join_records(self, - origin_pk: Any, - relationship: Model, - is_forward_relationship: bool) -> QuerySet: + def get_join_records( + self, origin_pk: Any, relationship: Model, is_forward_relationship: bool + ) -> QuerySet: """Get JoinRecords for relation on origin_pk in a certain direction.""" if utils.valid_uuid4(str(origin_pk)): pk_field = 'record_uuid' @@ -28,4 +29,6 @@ def get_join_records(self, if not is_forward_relationship: pk_field = 'related_' + pk_field - return self.filter(relationship=relationship).filter(**{pk_field: str(origin_pk)}) + return self.filter(relationship=relationship).filter( + **{pk_field: str(origin_pk)} + ) diff --git a/datamesh/migrations/0001_initial.py b/datamesh/migrations/0001_initial.py index 39d95848..efd991b5 100644 --- a/datamesh/migrations/0001_initial.py +++ b/datamesh/migrations/0001_initial.py @@ -9,41 +9,113 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( name='JoinRecord', fields=[ - ('join_record_uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ( + 'join_record_uuid', + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), ('record_id', models.PositiveIntegerField(blank=True, null=True)), ('record_uuid', models.UUIDField(blank=True, null=True)), - ('related_record_id', models.PositiveIntegerField(blank=True, null=True)), + ( + 'related_record_id', + models.PositiveIntegerField(blank=True, null=True), + ), ('related_record_uuid', models.UUIDField(blank=True, null=True)), ], ), migrations.CreateModel( name='LogicModuleModel', fields=[ - ('logic_module_model_uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('logic_module_endpoint_name', models.CharField(help_text='Without leading and trailing slashes', max_length=255)), + ( + 'logic_module_model_uuid', + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + 'logic_module_endpoint_name', + models.CharField( + help_text='Without leading and trailing slashes', max_length=255 + ), + ), ('model', models.CharField(max_length=128)), - ('endpoint', models.CharField(help_text="Endpoint of the model with leading and trailing slashs, p.e.: '/siteprofiles/'", max_length=255)), - ('lookup_field_name', models.SlugField(default='id', help_text="Name of the field in the model for detail methods, p.e.: 'id' or 'uuid'", max_length=64)), - ('is_local', models.BooleanField(default=False, help_text='Local model is taken from Buildly')), + ( + 'endpoint', + models.CharField( + help_text="Endpoint of the model with leading and trailing slashs, p.e.: '/siteprofiles/'", + max_length=255, + ), + ), + ( + 'lookup_field_name', + models.SlugField( + default='id', + help_text="Name of the field in the model for detail methods, p.e.: 'id' or 'uuid'", + max_length=64, + ), + ), + ( + 'is_local', + models.BooleanField( + default=False, help_text='Local model is taken from Buildly' + ), + ), ], options={ - 'unique_together': {('logic_module_endpoint_name', 'model'), ('logic_module_endpoint_name', 'endpoint')}, + 'unique_together': { + ('logic_module_endpoint_name', 'model'), + ('logic_module_endpoint_name', 'endpoint'), + } }, ), migrations.CreateModel( name='Relationship', fields=[ - ('key', models.SlugField(help_text="The key in the response body, where the related object data will be saved into, p.e.: 'contact_siteprofile_relationship'.", max_length=64)), - ('relationship_uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('origin_model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='joins_origins', to='datamesh.LogicModuleModel')), - ('related_model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='joins_relateds', to='datamesh.LogicModuleModel')), + ( + 'key', + models.SlugField( + help_text="The key in the response body, where the related object data will be saved into, p.e.: 'contact_siteprofile_relationship'.", + max_length=64, + ), + ), + ( + 'relationship_uuid', + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + 'origin_model', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='joins_origins', + to='datamesh.LogicModuleModel', + ), + ), + ( + 'related_model', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='joins_relateds', + to='datamesh.LogicModuleModel', + ), + ), ], ), ] diff --git a/datamesh/migrations/0002_auto_20190918_1659.py b/datamesh/migrations/0002_auto_20190918_1659.py index 83c0a747..040ddb46 100644 --- a/datamesh/migrations/0002_auto_20190918_1659.py +++ b/datamesh/migrations/0002_auto_20190918_1659.py @@ -8,21 +8,28 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ('core', '0001_initial'), - ('datamesh', '0001_initial'), - ] + dependencies = [('core', '0001_initial'), ('datamesh', '0001_initial')] operations = [ migrations.AddField( model_name='joinrecord', name='organization', - field=models.ForeignKey(blank=True, help_text='Related Organization with access', null=True, on_delete=django.db.models.deletion.CASCADE, to='core.Organization'), + field=models.ForeignKey( + blank=True, + help_text='Related Organization with access', + null=True, + on_delete=django.db.models.deletion.CASCADE, + to='core.Organization', + ), ), migrations.AddField( model_name='joinrecord', name='relationship', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='joinrecords', to='datamesh.Relationship'), + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='joinrecords', + to='datamesh.Relationship', + ), ), migrations.AlterUniqueTogether( name='relationship', @@ -30,26 +37,68 @@ class Migration(migrations.Migration): ), migrations.AddConstraint( model_name='joinrecord', - constraint=models.UniqueConstraint(condition=models.Q(('record_uuid', None), ('related_record_uuid', None)), fields=('relationship', 'record_id', 'related_record_id'), name='unique_join_record_ids'), + constraint=models.UniqueConstraint( + condition=models.Q( + ('record_uuid', None), ('related_record_uuid', None) + ), + fields=('relationship', 'record_id', 'related_record_id'), + name='unique_join_record_ids', + ), ), migrations.AddConstraint( model_name='joinrecord', - constraint=models.UniqueConstraint(condition=models.Q(('record_id', None), ('related_record_id', None)), fields=('relationship', 'record_uuid', 'related_record_uuid'), name='unique_join_record_uuids'), + constraint=models.UniqueConstraint( + condition=models.Q(('record_id', None), ('related_record_id', None)), + fields=('relationship', 'record_uuid', 'related_record_uuid'), + name='unique_join_record_uuids', + ), ), migrations.AddConstraint( model_name='joinrecord', - constraint=models.UniqueConstraint(condition=models.Q(('record_uuid', None), ('related_record_id', None)), fields=('relationship', 'record_id', 'related_record_uuid'), name='unique_join_record_id_uuid'), + constraint=models.UniqueConstraint( + condition=models.Q(('record_uuid', None), ('related_record_id', None)), + fields=('relationship', 'record_id', 'related_record_uuid'), + name='unique_join_record_id_uuid', + ), ), migrations.AddConstraint( model_name='joinrecord', - constraint=models.UniqueConstraint(condition=models.Q(('record_id', None), ('related_record_uuid', None)), fields=('relationship', 'record_uuid', 'related_record_id'), name='unique_join_record_uuid_id'), + constraint=models.UniqueConstraint( + condition=models.Q(('record_id', None), ('related_record_uuid', None)), + fields=('relationship', 'record_uuid', 'related_record_id'), + name='unique_join_record_uuid_id', + ), ), migrations.AddConstraint( model_name='joinrecord', - constraint=models.CheckConstraint(check=models.Q(models.Q(('record_id', None), ('record_uuid', None), _negated=True), models.Q(models.Q(_negated=True, record_id=None), models.Q(_negated=True, record_uuid=None), _negated=True)), name='one_record_primary_key'), + constraint=models.CheckConstraint( + check=models.Q( + models.Q(('record_id', None), ('record_uuid', None), _negated=True), + models.Q( + models.Q(_negated=True, record_id=None), + models.Q(_negated=True, record_uuid=None), + _negated=True, + ), + ), + name='one_record_primary_key', + ), ), migrations.AddConstraint( model_name='joinrecord', - constraint=models.CheckConstraint(check=models.Q(models.Q(('related_record_id', None), ('related_record_uuid', None), _negated=True), models.Q(models.Q(_negated=True, related_record_id=None), models.Q(_negated=True, related_record_uuid=None), _negated=True)), name='one_related_record_primary_key'), + constraint=models.CheckConstraint( + check=models.Q( + models.Q( + ('related_record_id', None), + ('related_record_uuid', None), + _negated=True, + ), + models.Q( + models.Q(_negated=True, related_record_id=None), + models.Q(_negated=True, related_record_uuid=None), + _negated=True, + ), + ), + name='one_related_record_primary_key', + ), ), ] diff --git a/datamesh/mixins.py b/datamesh/mixins.py index 1e9b851c..83480f89 100644 --- a/datamesh/mixins.py +++ b/datamesh/mixins.py @@ -1,10 +1,9 @@ - - class OrganizationQuerySetMixin(object): """ Adds functionality to return a queryset filtered by the organization_uuid in the JWT header. If no jwt header is given, an empty queryset will be returned. """ + def get_queryset(self): queryset = super().get_queryset() organization_uuid = self.request.session.get('jwt_organization_uuid', None) diff --git a/datamesh/models.py b/datamesh/models.py index 88d18de9..e2ec16ff 100644 --- a/datamesh/models.py +++ b/datamesh/models.py @@ -10,19 +10,32 @@ class LogicModuleModel(models.Model): - logic_module_model_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - logic_module_endpoint_name = models.CharField(max_length=255, help_text="Without leading and trailing slashes") + logic_module_model_uuid = models.UUIDField( + primary_key=True, default=uuid.uuid4, editable=False + ) + logic_module_endpoint_name = models.CharField( + max_length=255, help_text="Without leading and trailing slashes" + ) model = models.CharField(max_length=128) - endpoint = models.CharField(max_length=255, help_text="Endpoint of the model with leading and trailing slashs, p.e.: '/siteprofiles/'") - lookup_field_name = models.SlugField(max_length=64, default='id', help_text="Name of the field in the model for detail methods, p.e.: 'id' or 'uuid'") - is_local = models.BooleanField(default=False, help_text="Local model is taken from Buildly") + endpoint = models.CharField( + max_length=255, + help_text="Endpoint of the model with leading and trailing slashs, p.e.: '/siteprofiles/'", + ) + lookup_field_name = models.SlugField( + max_length=64, + default='id', + help_text="Name of the field in the model for detail methods, p.e.: 'id' or 'uuid'", + ) + is_local = models.BooleanField( + default=False, help_text="Local model is taken from Buildly" + ) objects = LogicModuleModelManager() class Meta: unique_together = ( ('logic_module_endpoint_name', 'model'), - ('logic_module_endpoint_name', 'endpoint') + ('logic_module_endpoint_name', 'endpoint'), ) def __str__(self): @@ -40,8 +53,9 @@ def get_relationships(self) -> List[Tuple[models.Model, bool]]: ) relationships_with_direction = list() for relationship in relationships: - relationships_with_direction.append((relationship, - relationship.origin_model == self)) + relationships_with_direction.append( + (relationship, relationship.origin_model == self) + ) return relationships_with_direction @@ -66,7 +80,9 @@ def __str__(self): def validate_reverse_relationship_absence(self): """Validate reverse relationship does not exist already.""" - if self.__class__.objects.filter(origin_model=self.related_model, related_model=self.origin_model).count(): + if self.__class__.objects.filter( + origin_model=self.related_model, related_model=self.origin_model + ).count(): raise ValidationError("Reverse relationship already exists.") def save(self, *args, **kwargs): @@ -74,21 +90,27 @@ def save(self, *args, **kwargs): super().save(*args, **kwargs) class Meta: - unique_together = ( - 'relationship_uuid', - 'origin_model', - 'related_model', - ) + unique_together = ('relationship_uuid', 'origin_model', 'related_model') class JoinRecord(models.Model): - join_record_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - relationship = models.ForeignKey(Relationship, related_name='joinrecords', on_delete=models.CASCADE) + join_record_uuid = models.UUIDField( + primary_key=True, default=uuid.uuid4, editable=False + ) + relationship = models.ForeignKey( + Relationship, related_name='joinrecords', on_delete=models.CASCADE + ) record_id = models.PositiveIntegerField(blank=True, null=True) record_uuid = models.UUIDField(blank=True, null=True) related_record_id = models.PositiveIntegerField(blank=True, null=True) related_record_uuid = models.UUIDField(blank=True, null=True) - organization = models.ForeignKey(Organization, null=True, blank=True, help_text="Related Organization with access", on_delete=models.CASCADE) + organization = models.ForeignKey( + Organization, + null=True, + blank=True, + help_text="Related Organization with access", + on_delete=models.CASCADE, + ) objects = JoinRecordManager() @@ -97,52 +119,39 @@ class Meta: # 'record_id', 'record_uuid', 'related_record_id', 'related_record_uuid' constraints = [ UniqueConstraint( - fields=( - 'relationship', - 'record_id', - 'related_record_id', - ), + fields=('relationship', 'record_id', 'related_record_id'), name='unique_join_record_ids', - condition=Q(record_uuid=None, related_record_uuid=None) + condition=Q(record_uuid=None, related_record_uuid=None), ), UniqueConstraint( - fields=( - 'relationship', - 'record_uuid', - 'related_record_uuid', - ), + fields=('relationship', 'record_uuid', 'related_record_uuid'), name='unique_join_record_uuids', - condition=Q(record_id=None, related_record_id=None) + condition=Q(record_id=None, related_record_id=None), ), UniqueConstraint( - fields=( - 'relationship', - 'record_id', - 'related_record_uuid', - ), + fields=('relationship', 'record_id', 'related_record_uuid'), name='unique_join_record_id_uuid', - condition=Q(record_uuid=None, related_record_id=None) + condition=Q(record_uuid=None, related_record_id=None), ), UniqueConstraint( - fields=( - 'relationship', - 'record_uuid', - 'related_record_id', - ), + fields=('relationship', 'record_uuid', 'related_record_id'), name='unique_join_record_uuid_id', - condition=Q(record_id=None, related_record_uuid=None) + condition=Q(record_id=None, related_record_uuid=None), ), CheckConstraint( name='one_record_primary_key', - check=~Q(record_id=None, record_uuid=None) & (~(~Q(record_id=None) & ~Q(record_uuid=None))) + check=~Q(record_id=None, record_uuid=None) + & (~(~Q(record_id=None) & ~Q(record_uuid=None))), ), CheckConstraint( name='one_related_record_primary_key', - check=~Q(related_record_id=None, related_record_uuid=None) & ( - ~(~Q(related_record_id=None) & ~Q(related_record_uuid=None))) + check=~Q(related_record_id=None, related_record_uuid=None) + & (~(~Q(related_record_id=None) & ~Q(related_record_uuid=None))), ), ] def __str__(self): - return f'{self.relationship} - ' \ + return ( + f'{self.relationship} - ' f'{self.record_id or self.record_uuid} -> {self.related_record_id or self.related_record_uuid}' + ) diff --git a/datamesh/serializers.py b/datamesh/serializers.py index 6b5b3e36..a3b390ea 100644 --- a/datamesh/serializers.py +++ b/datamesh/serializers.py @@ -6,7 +6,6 @@ class LogicModuleModelSerializer(serializers.ModelSerializer): - class Meta: model = LogicModuleModel fields = '__all__' @@ -34,11 +33,15 @@ class JoinRecordSerializer(serializers.ModelSerializer): _model_choices_map = dict() origin_model_name = serializers.ChoiceField(choices=_model_choices, write_only=True) - related_model_name = serializers.ChoiceField(choices=_model_choices, write_only=True) + related_model_name = serializers.ChoiceField( + choices=_model_choices, write_only=True + ) def __init__(self, *args, **kwargs): """Define the choices for valid LogicModuleModels.""" - for model in LogicModuleModel.objects.all().values('logic_module_endpoint_name', 'model', 'pk'): + for model in LogicModuleModel.objects.all().values( + 'logic_module_endpoint_name', 'model', 'pk' + ): choice = model['logic_module_endpoint_name'] + model['model'] self._model_choices.append(choice) self._model_choices_map.update({choice: model['pk']}) @@ -46,27 +49,35 @@ def __init__(self, *args, **kwargs): def create(self, validated_data: dict) -> JoinRecord: """Get logic_module_models, get_or_create `Relationship`s and save in case it is not already existing.""" - origin_model_pk = self._model_choices_map[validated_data.pop('origin_model_name')] - related_model_pk = self._model_choices_map[validated_data.pop('related_model_name')] + origin_model_pk = self._model_choices_map[ + validated_data.pop('origin_model_name') + ] + related_model_pk = self._model_choices_map[ + validated_data.pop('related_model_name') + ] relationship, _ = Relationship.objects.get_or_create( - origin_model_id=origin_model_pk, - related_model_id=related_model_pk + origin_model_id=origin_model_pk, related_model_id=related_model_pk + ) + organization_uuid = self.context['request'].session.get( + 'jwt_organization_uuid', None ) - organization_uuid = self.context['request'].session.get('jwt_organization_uuid', None) join_record, _ = JoinRecord.objects.get_or_create( relationship=relationship, organization_id=organization_uuid, - **validated_data + **validated_data, ) return join_record def update(self, instance: JoinRecord, validated_data: dict) -> JoinRecord: """Automatically set the relationship from the passed models.""" - origin_model_pk = self._model_choices_map[validated_data.pop('origin_model_name')] - related_model_pk = self._model_choices_map[validated_data.pop('related_model_name')] + origin_model_pk = self._model_choices_map[ + validated_data.pop('origin_model_name') + ] + related_model_pk = self._model_choices_map[ + validated_data.pop('related_model_name') + ] relationship, _ = Relationship.objects.get_or_create( - origin_model_id=origin_model_pk, - related_model_id=related_model_pk + origin_model_id=origin_model_pk, related_model_id=related_model_pk ) instance.relationship = relationship for key, value in validated_data.items(): @@ -78,13 +89,16 @@ def to_representation(self, instance: JoinRecord) -> OrderedDict: """Add origin_model_name and related_model_name.""" ret_repr = super().to_representation(instance) ret_repr.update( - {'origin_model_name': f'{instance.relationship.origin_model.logic_module_endpoint_name}' + { + 'origin_model_name': f'{instance.relationship.origin_model.logic_module_endpoint_name}' f'{instance.relationship.origin_model.model}', - 'related_model_name': f'{instance.relationship.related_model.logic_module_endpoint_name}' - f'{instance.relationship.related_model.model}'}) + 'related_model_name': f'{instance.relationship.related_model.logic_module_endpoint_name}' + f'{instance.relationship.related_model.model}', + } + ) return ret_repr class Meta: model = JoinRecord - exclude = ('relationship', ) - read_only_fields = ('organization', ) + exclude = ('relationship',) + read_only_fields = ('organization',) diff --git a/datamesh/services.py b/datamesh/services.py index 36d20d1b..3a6c08eb 100644 --- a/datamesh/services.py +++ b/datamesh/services.py @@ -18,9 +18,15 @@ class DataMesh: For each model DataMesh object should be created. """ - def __init__(self, logic_module_endpoint: str, model_endpoint: str, access_validator: Any = None): - self._logic_module_model = LogicModuleModel.objects.get(logic_module_endpoint_name=logic_module_endpoint, - endpoint=model_endpoint) + def __init__( + self, + logic_module_endpoint: str, + model_endpoint: str, + access_validator: Any = None, + ): + self._logic_module_model = LogicModuleModel.objects.get( + logic_module_endpoint_name=logic_module_endpoint, endpoint=model_endpoint + ) self._relationships = self._logic_module_model.get_relationships() self._origin_lookup_field = self._logic_module_model.lookup_field_name self._access_validator = access_validator @@ -33,11 +39,16 @@ def related_logic_modules(self) -> list: The origin_model has to be added for the symmetrical/reverse relationships. """ if not hasattr(self, '_related_logic_modules'): - modules_list = [relationship.related_model.logic_module_endpoint_name - for relationship, _ in self._relationships if not relationship.related_model.is_local] + modules_list = [ + relationship.related_model.logic_module_endpoint_name + for relationship, _ in self._relationships + if not relationship.related_model.is_local + ] modules_list_reverse = [ relationship.origin_model.logic_module_endpoint_name - for relationship, _ in self._relationships if not relationship.origin_model.is_local] + for relationship, _ in self._relationships + if not relationship.origin_model.is_local + ] self._related_logic_modules = set(modules_list + modules_list_reverse) return self._related_logic_modules @@ -46,10 +57,13 @@ def get_related_records_meta(self, origin_pk: Any) -> Generator[tuple, None, Non Gets list of related records' META-data that is used for retrieving data for each of these records """ for relationship, is_forward_lookup in self._relationships: - join_records = JoinRecord.objects.get_join_records(origin_pk, relationship, is_forward_lookup) + join_records = JoinRecord.objects.get_join_records( + origin_pk, relationship, is_forward_lookup + ) if join_records: related_model, related_record_field = prepare_lookup_kwargs( - is_forward_lookup, relationship, join_records[0]) + is_forward_lookup, relationship, join_records[0] + ) for join_record in join_records: params = { @@ -73,19 +87,21 @@ def extend_data(self, data: Union[dict, list], client_map: Dict[str, Any]) -> No for data_item in data: self._add_nested_data(data_item, client_map) - def _extend_with_local(self, data_item: dict, relationship: Relationship, params: dict) -> None: + def _extend_with_local( + self, data_item: dict, relationship: Relationship, params: dict + ) -> None: """ Extend data from local object (via Django ORM query)""" cache_key = f"{params['service']}.{params['model']}.{params['pk']}" if cache_key in self._cache: data_item[relationship.key].append(self._cache[cache_key]) return try: - model = apps.get_model(app_label=params['service'], model_name=params['model']) + model = apps.get_model( + app_label=params['service'], model_name=params['model'] + ) except LookupError as e: raise DatameshConfigurationError(f'Data Mesh configuration error: {e}') - lookup = { - params['pk_name']: params['pk'] - } + lookup = {params['pk_name']: params['pk']} try: obj = model.objects.get(**lookup) except model.DoesNotExist as e: @@ -93,10 +109,18 @@ def _extend_with_local(self, data_item: dict, relationship: Relationship, params else: # TODO: need to validate object access, like utils.validate_object_access(request, obj) if self._access_validator: - if hasattr(self._access_validator, 'validate') and callable(self._access_validator.validate): + if hasattr(self._access_validator, 'validate') and callable( + self._access_validator.validate + ): self._access_validator.validate(obj) else: +<<<<<<< HEAD + raise DatameshConfigurationError( + f'{"DataMesh Error:Access Validator should have validate method"}' + ) +======= raise DatameshConfigurationError(f'{"DataMesh Error:Access Validator should have validate method"}') +>>>>>>> master obj_dict = model_to_dict(obj) data_item[relationship.key].append(obj_dict) self._cache[cache_key] = obj_dict @@ -132,9 +156,17 @@ def _add_nested_data(self, data_item: dict, client_map: Dict[str, Any]) -> None: if isinstance(content, dict): data_item[relationship.key].append(dict(content)) else: - logger.error(f'No response data for join record (request params: {params})') + logger.error( + f'No response data for join record (request params: {params})' + ) else: +<<<<<<< HEAD + raise DatameshConfigurationError( + f'{"DataMesh Error: Client should have request method"}' + ) +======= raise DatameshConfigurationError(f'{"DataMesh Error: Client should have request method"}') +>>>>>>> master def fetch_datamesh_relationship(self): relationship_list = [] @@ -198,17 +230,25 @@ async def _prepare_tasks(self, data_item: dict, client_map: Dict[str, Any]) -> l params['method'] = 'get' client = client_map.get(params['service']) - tasks.append(self._extend_content(client, data_item[relationship.key], **params)) + tasks.append( + self._extend_content(client, data_item[relationship.key], **params) + ) return tasks - async def _extend_content(self, client: Any, placeholder: list, **request_kwargs) -> None: + async def _extend_content( + self, client: Any, placeholder: list, **request_kwargs + ) -> None: """ Performs data request and extends data with received data """ content = await client.request(**request_kwargs) - if isinstance(content, tuple): # assume that response body is the first returned value + if isinstance( + content, tuple + ): # assume that response body is the first returned value content = content[0] if isinstance(content, dict): placeholder.append(dict(content)) else: - logger.error(f'No response data for join record (request params: {request_kwargs})') + logger.error( + f'No response data for join record (request params: {request_kwargs})' + ) diff --git a/datamesh/tests/fixtures.py b/datamesh/tests/fixtures.py index bf2d3b59..bb6251e3 100644 --- a/datamesh/tests/fixtures.py +++ b/datamesh/tests/fixtures.py @@ -13,68 +13,105 @@ def join_record(): @pytest.fixture def relationship(): lm = factories.LogicModule(name='Products Service', endpoint_name='products') - lmm = factories.LogicModuleModel(logic_module_endpoint_name=lm.endpoint_name, - model='Product', endpoint='/products/') - lm_document = factories.LogicModule(name='Document Service', endpoint_name='documents') - lmm_document = factories.LogicModuleModel(logic_module_endpoint_name=lm_document.endpoint_name, - model='Document', endpoint='/documents/') - return factories.Relationship(origin_model=lmm, related_model=lmm_document, key='product_document_relationship') + lmm = factories.LogicModuleModel( + logic_module_endpoint_name=lm.endpoint_name, + model='Product', + endpoint='/products/', + ) + lm_document = factories.LogicModule( + name='Document Service', endpoint_name='documents' + ) + lmm_document = factories.LogicModuleModel( + logic_module_endpoint_name=lm_document.endpoint_name, + model='Document', + endpoint='/documents/', + ) + return factories.Relationship( + origin_model=lmm, + related_model=lmm_document, + key='product_document_relationship', + ) @pytest.fixture def relationship2(relationship): lmm = relationship.origin_model # Products model from 1st relationship - lm_location = factories.LogicModule(name='Location Service', endpoint_name='location') - lmm_location = factories.LogicModuleModel(logic_module_endpoint_name=lm_location.endpoint_name, - model='Location', endpoint='/siteprofile/') - return factories.Relationship(origin_model=lmm, related_model=lmm_location, key='location_relationship') + lm_location = factories.LogicModule( + name='Location Service', endpoint_name='location' + ) + lmm_location = factories.LogicModuleModel( + logic_module_endpoint_name=lm_location.endpoint_name, + model='Location', + endpoint='/siteprofile/', + ) + return factories.Relationship( + origin_model=lmm, related_model=lmm_location, key='location_relationship' + ) @pytest.fixture def relationship_with_10_records(org): lm = factories.LogicModule(name='Products Service', endpoint_name='products') - lmm = factories.LogicModuleModel(logic_module_endpoint_name=lm.endpoint_name, - model='Product', endpoint='/products/', lookup_field_name='uuid') - lm_document = factories.LogicModule(name='Document Service', endpoint_name='documents') - lmm_document = factories.LogicModuleModel(logic_module_endpoint_name=lm_document.endpoint_name, - model='Document', endpoint='/documents/', - lookup_field_name='uuid') - relationship = factories.Relationship(origin_model=lmm, related_model=lmm_document, key='product_document_relationship') + lmm = factories.LogicModuleModel( + logic_module_endpoint_name=lm.endpoint_name, + model='Product', + endpoint='/products/', + lookup_field_name='uuid', + ) + lm_document = factories.LogicModule( + name='Document Service', endpoint_name='documents' + ) + lmm_document = factories.LogicModuleModel( + logic_module_endpoint_name=lm_document.endpoint_name, + model='Document', + endpoint='/documents/', + lookup_field_name='uuid', + ) + relationship = factories.Relationship( + origin_model=lmm, + related_model=lmm_document, + key='product_document_relationship', + ) for _ in range(10): - factories.JoinRecord.create(relationship=relationship, - record_uuid=uuid.uuid4(), record_id=None, - related_record_uuid=uuid.uuid4(), related_record_id=None, - organization=org) + factories.JoinRecord.create( + relationship=relationship, + record_uuid=uuid.uuid4(), + record_id=None, + related_record_uuid=uuid.uuid4(), + related_record_id=None, + organization=org, + ) return relationship @pytest.fixture def relationship_with_local(): lm = factories.LogicModule(name='Products Service', endpoint_name='products') - lmm = factories.LogicModuleModel(logic_module_endpoint_name=lm.endpoint_name, - model='Product', endpoint='/products/') - lmm_org = factories.LogicModuleModel(logic_module_endpoint_name='core', - model='Organization', - endpoint='/organization/', - lookup_field_name='organization_uuid', - is_local=True) - return factories.Relationship(origin_model=lmm, related_model=lmm_org, key='product_document_relationship') + lmm = factories.LogicModuleModel( + logic_module_endpoint_name=lm.endpoint_name, + model='Product', + endpoint='/products/', + ) + lmm_org = factories.LogicModuleModel( + logic_module_endpoint_name='core', + model='Organization', + endpoint='/organization/', + lookup_field_name='organization_uuid', + is_local=True, + ) + return factories.Relationship( + origin_model=lmm, related_model=lmm_org, key='product_document_relationship' + ) @pytest.fixture def document_logic_module(): - return factories.LogicModule( - name='document', - endpoint_name='document' - ) + return factories.LogicModule(name='document', endpoint_name='document') @pytest.fixture def crm_logic_module(): - return factories.LogicModule( - name='crm', - endpoint_name='crm' - ) + return factories.LogicModule(name='crm', endpoint_name='crm') @pytest.fixture @@ -85,14 +122,12 @@ def logic_module_model(): @pytest.fixture def document_logic_module_model(): return factories.LogicModuleModel( - logic_module_endpoint_name='document', - model='Document' + logic_module_endpoint_name='document', model='Document' ) @pytest.fixture def appointment_logic_module_model(): return factories.LogicModuleModel( - logic_module_endpoint_name='crm', - model='Appointment' + logic_module_endpoint_name='crm', model='Appointment' ) diff --git a/datamesh/tests/test_datamesh_service.py b/datamesh/tests/test_datamesh_service.py index f644121d..d88bb5b5 100644 --- a/datamesh/tests/test_datamesh_service.py +++ b/datamesh/tests/test_datamesh_service.py @@ -5,16 +5,25 @@ import factories from core.tests.fixtures import org -from datamesh.tests.fixtures import relationship, relationship2, relationship_with_10_records, relationship_with_local +from datamesh.tests.fixtures import ( + relationship, + relationship2, + relationship_with_10_records, + relationship_with_local, +) from datamesh.services import DataMesh @pytest.mark.django_db() class TestSyncDataMesh: - def test_join_data_one_obj_w_relationships(self, relationship): - factories.JoinRecord(relationship=relationship, record_id=1, related_record_id=2, - record_uuid=None, related_record_uuid=None) + factories.JoinRecord( + relationship=relationship, + record_id=1, + related_record_id=2, + record_uuid=None, + related_record_uuid=None, + ) logic_module_model = relationship.origin_model data = {'id': 1, 'name': 'test', 'contact_uuid': 1} @@ -22,11 +31,16 @@ def test_join_data_one_obj_w_relationships(self, relationship): # mock client for related logic module class ClientMock: def request(self, **kwargs): - return {'id': 1, 'file': '/somewhere/128/',} - client_map = {relationship.related_model.logic_module_endpoint_name: ClientMock()} + return {'id': 1, 'file': '/somewhere/128/'} - datamesh = DataMesh(logic_module_endpoint=logic_module_model.logic_module_endpoint_name, - model_endpoint=logic_module_model.endpoint) + client_map = { + relationship.related_model.logic_module_endpoint_name: ClientMock() + } + + datamesh = DataMesh( + logic_module_endpoint=logic_module_model.logic_module_endpoint_name, + model_endpoint=logic_module_model.endpoint, + ) datamesh.extend_data(data, client_map) # validate result @@ -34,19 +48,28 @@ def request(self, **kwargs): 'id': 1, 'name': 'test', 'contact_uuid': 1, - relationship.key: [{ - 'id': 1, - 'file': '/somewhere/128/', - }] + relationship.key: [{'id': 1, 'file': '/somewhere/128/'}], } assert data == expected_data - def test_join_data_one_obj_w_two_relationships(self, relationship, relationship2, org): - factories.JoinRecord(relationship=relationship, record_id=1, related_record_id=2, - record_uuid=None, related_record_uuid=None) - factories.JoinRecord(relationship=relationship2, record_id=1, related_record_id=10, - record_uuid=None, related_record_uuid=None) + def test_join_data_one_obj_w_two_relationships( + self, relationship, relationship2, org + ): + factories.JoinRecord( + relationship=relationship, + record_id=1, + related_record_id=2, + record_uuid=None, + related_record_uuid=None, + ) + factories.JoinRecord( + relationship=relationship2, + record_id=1, + related_record_id=10, + record_uuid=None, + related_record_uuid=None, + ) logic_module_model = relationship.origin_model data = {'id': 1, 'name': 'test', 'contact_uuid': 1} @@ -59,13 +82,16 @@ def request(self, **kwargs): if kwargs['model'] == 'siteprofile': return {'id': 10, 'city': 'New York'} return {} + client_map = { relationship.related_model.logic_module_endpoint_name: ClientMock(), relationship2.related_model.logic_module_endpoint_name: ClientMock(), } - datamesh = DataMesh(logic_module_endpoint=logic_module_model.logic_module_endpoint_name, - model_endpoint=logic_module_model.endpoint) + datamesh = DataMesh( + logic_module_endpoint=logic_module_model.logic_module_endpoint_name, + model_endpoint=logic_module_model.endpoint, + ) datamesh.extend_data(data, client_map) # validate result @@ -73,14 +99,8 @@ def request(self, **kwargs): 'id': 1, 'name': 'test', 'contact_uuid': 1, - relationship.key: [{ - 'id': 2, - 'file': '/documents/128/', - }], - relationship2.key: [{ - 'id': 10, - 'city': 'New York', - }] + relationship.key: [{'id': 2, 'file': '/documents/128/'}], + relationship2.key: [{'id': 10, 'city': 'New York'}], } assert data == expected_data @@ -90,18 +110,31 @@ def test_join_data_list(self, relationship_with_10_records): logic_module_model = relationship_with_10_records.origin_model - data = [{'uuid': str(item.record_uuid), 'name': f'Boiler #{i}'} for i, item in enumerate(join_records)] + data = [ + {'uuid': str(item.record_uuid), 'name': f'Boiler #{i}'} + for i, item in enumerate(join_records) + ] # mock client for related logic module - mocked_response_data = {str(item.related_record_uuid): '/documents/128/' for item in join_records} + mocked_response_data = { + str(item.related_record_uuid): '/documents/128/' for item in join_records + } class ClientMock: def request(self, **kwargs): - return {'uuid': kwargs['pk'], 'file': mocked_response_data[kwargs['pk']]} - client_map = {relationship_with_10_records.related_model.logic_module_endpoint_name: ClientMock()} + return { + 'uuid': kwargs['pk'], + 'file': mocked_response_data[kwargs['pk']], + } + + client_map = { + relationship_with_10_records.related_model.logic_module_endpoint_name: ClientMock() + } - datamesh = DataMesh(logic_module_endpoint=logic_module_model.logic_module_endpoint_name, - model_endpoint=logic_module_model.endpoint) + datamesh = DataMesh( + logic_module_endpoint=logic_module_model.logic_module_endpoint_name, + model_endpoint=logic_module_model.endpoint, + ) datamesh.extend_data(data, client_map) for i, item in enumerate(data): @@ -112,15 +145,21 @@ def request(self, **kwargs): assert nested[0]['uuid'] == str(join_records[i].related_record_uuid) def test_relationship_with_local_lm(self, relationship_with_local, org): - factories.JoinRecord(relationship=relationship_with_local, record_id=1, - related_record_uuid=org.organization_uuid, - record_uuid=None, related_record_id=None) + factories.JoinRecord( + relationship=relationship_with_local, + record_id=1, + related_record_uuid=org.organization_uuid, + record_uuid=None, + related_record_id=None, + ) logic_module_model = relationship_with_local.origin_model data = {'id': 1, 'name': 'test', 'contact_uuid': 1} - datamesh = DataMesh(logic_module_endpoint=logic_module_model.logic_module_endpoint_name, - model_endpoint=logic_module_model.endpoint) + datamesh = DataMesh( + logic_module_endpoint=logic_module_model.logic_module_endpoint_name, + model_endpoint=logic_module_model.endpoint, + ) datamesh.extend_data(data, {}) # validate result @@ -128,7 +167,7 @@ def test_relationship_with_local_lm(self, relationship_with_local, org): 'id': 1, 'name': 'test', 'contact_uuid': 1, - relationship_with_local.key: [model_to_dict(org)] + relationship_with_local.key: [model_to_dict(org)], } assert data == expected_data @@ -136,10 +175,14 @@ def test_relationship_with_local_lm(self, relationship_with_local, org): @pytest.mark.django_db() class TestAsyncDataMesh: - def test_join_data_one_obj_w_relationships(self, relationship): - factories.JoinRecord(relationship=relationship, record_id=1, related_record_id=2, - record_uuid=None, related_record_uuid=None) + factories.JoinRecord( + relationship=relationship, + record_id=1, + related_record_id=2, + record_uuid=None, + related_record_uuid=None, + ) logic_module_model = relationship.origin_model data = {'id': 1, 'name': 'test', 'contact_uuid': 1} @@ -147,11 +190,16 @@ def test_join_data_one_obj_w_relationships(self, relationship): # mock client for related logic module class ClientMock: async def request(self, **kwargs): - return {'id': 1, 'file': '/somewhere/128/',} - client_map = {relationship.related_model.logic_module_endpoint_name: ClientMock()} + return {'id': 1, 'file': '/somewhere/128/'} - datamesh = DataMesh(logic_module_endpoint=logic_module_model.logic_module_endpoint_name, - model_endpoint=logic_module_model.endpoint) + client_map = { + relationship.related_model.logic_module_endpoint_name: ClientMock() + } + + datamesh = DataMesh( + logic_module_endpoint=logic_module_model.logic_module_endpoint_name, + model_endpoint=logic_module_model.endpoint, + ) asyncio.run(datamesh.async_extend_data(data, client_map)) @@ -160,19 +208,28 @@ async def request(self, **kwargs): 'id': 1, 'name': 'test', 'contact_uuid': 1, - relationship.key: [{ - 'id': 1, - 'file': '/somewhere/128/', - }] + relationship.key: [{'id': 1, 'file': '/somewhere/128/'}], } assert data == expected_data - def test_join_data_one_obj_w_two_relationships(self, relationship, relationship2, org): - factories.JoinRecord(relationship=relationship, record_id=1, related_record_id=2, - record_uuid=None, related_record_uuid=None) - factories.JoinRecord(relationship=relationship2, record_id=1, related_record_id=10, - record_uuid=None, related_record_uuid=None) + def test_join_data_one_obj_w_two_relationships( + self, relationship, relationship2, org + ): + factories.JoinRecord( + relationship=relationship, + record_id=1, + related_record_id=2, + record_uuid=None, + related_record_uuid=None, + ) + factories.JoinRecord( + relationship=relationship2, + record_id=1, + related_record_id=10, + record_uuid=None, + related_record_uuid=None, + ) logic_module_model = relationship.origin_model data = {'id': 1, 'name': 'test', 'contact_uuid': 1} @@ -185,13 +242,16 @@ async def request(self, **kwargs): if kwargs['model'] == 'siteprofile': return {'id': 10, 'city': 'New York'} return {} + client_map = { relationship.related_model.logic_module_endpoint_name: ClientMock(), relationship2.related_model.logic_module_endpoint_name: ClientMock(), } - datamesh = DataMesh(logic_module_endpoint=logic_module_model.logic_module_endpoint_name, - model_endpoint=logic_module_model.endpoint) + datamesh = DataMesh( + logic_module_endpoint=logic_module_model.logic_module_endpoint_name, + model_endpoint=logic_module_model.endpoint, + ) asyncio.run(datamesh.async_extend_data(data, client_map)) # validate result @@ -199,14 +259,8 @@ async def request(self, **kwargs): 'id': 1, 'name': 'test', 'contact_uuid': 1, - relationship.key: [{ - 'id': 2, - 'file': '/documents/128/', - }], - relationship2.key: [{ - 'id': 10, - 'city': 'New York', - }] + relationship.key: [{'id': 2, 'file': '/documents/128/'}], + relationship2.key: [{'id': 10, 'city': 'New York'}], } assert data == expected_data @@ -216,18 +270,31 @@ def test_join_data_list(self, relationship_with_10_records): logic_module_model = relationship_with_10_records.origin_model - data = [{'uuid': str(item.record_uuid), 'name': f'Boiler #{i}'} for i, item in enumerate(join_records)] + data = [ + {'uuid': str(item.record_uuid), 'name': f'Boiler #{i}'} + for i, item in enumerate(join_records) + ] # mock client for related logic module - mocked_response_data = {str(item.related_record_uuid): '/documents/128/' for item in join_records} + mocked_response_data = { + str(item.related_record_uuid): '/documents/128/' for item in join_records + } class ClientMock: async def request(self, **kwargs): - return {'uuid': kwargs['pk'], 'file': mocked_response_data[kwargs['pk']]} - client_map = {relationship_with_10_records.related_model.logic_module_endpoint_name: ClientMock()} + return { + 'uuid': kwargs['pk'], + 'file': mocked_response_data[kwargs['pk']], + } + + client_map = { + relationship_with_10_records.related_model.logic_module_endpoint_name: ClientMock() + } - datamesh = DataMesh(logic_module_endpoint=logic_module_model.logic_module_endpoint_name, - model_endpoint=logic_module_model.endpoint) + datamesh = DataMesh( + logic_module_endpoint=logic_module_model.logic_module_endpoint_name, + model_endpoint=logic_module_model.endpoint, + ) asyncio.run(datamesh.async_extend_data(data, client_map)) for i, item in enumerate(data): @@ -238,15 +305,21 @@ async def request(self, **kwargs): assert nested[0]['uuid'] == str(join_records[i].related_record_uuid) def test_relationship_with_local_lm(self, relationship_with_local, org): - factories.JoinRecord(relationship=relationship_with_local, record_id=1, - related_record_uuid=org.organization_uuid, - record_uuid=None, related_record_id=None) + factories.JoinRecord( + relationship=relationship_with_local, + record_id=1, + related_record_uuid=org.organization_uuid, + record_uuid=None, + related_record_id=None, + ) logic_module_model = relationship_with_local.origin_model data = {'id': 1, 'name': 'test', 'contact_uuid': 1} - datamesh = DataMesh(logic_module_endpoint=logic_module_model.logic_module_endpoint_name, - model_endpoint=logic_module_model.endpoint) + datamesh = DataMesh( + logic_module_endpoint=logic_module_model.logic_module_endpoint_name, + model_endpoint=logic_module_model.endpoint, + ) asyncio.run(datamesh.async_extend_data(data, {})) # validate result @@ -254,7 +327,7 @@ def test_relationship_with_local_lm(self, relationship_with_local, org): 'id': 1, 'name': 'test', 'contact_uuid': 1, - relationship_with_local.key: [model_to_dict(org)] + relationship_with_local.key: [model_to_dict(org)], } assert data == expected_data diff --git a/datamesh/tests/test_join.py b/datamesh/tests/test_join.py index 3e076397..7376c316 100644 --- a/datamesh/tests/test_join.py +++ b/datamesh/tests/test_join.py @@ -5,28 +5,47 @@ from bravado_core.spec import Spec import factories -from datamesh.tests.fixtures import relationship, relationship2, relationship_with_10_records +from datamesh.tests.fixtures import ( + relationship, + relationship2, + relationship_with_10_records, +) from core.tests.fixtures import auth_api_client, org @pytest.mark.django_db() @patch('gateway.request.GatewayRequest._get_swagger_spec') @patch('gateway.request.SwaggerClient.request') -def test_join_data_one_obj_w_relationships(mock_perform_request, mock_spec, auth_api_client, relationship): - factories.JoinRecord(relationship=relationship, record_id=1, related_record_id=2, - record_uuid=None, related_record_uuid=None) +def test_join_data_one_obj_w_relationships( + mock_perform_request, mock_spec, auth_api_client, relationship +): + factories.JoinRecord( + relationship=relationship, + record_id=1, + related_record_id=2, + record_uuid=None, + related_record_uuid=None, + ) # mock app mock_spec.return_value = Mock(Spec) # mock first response - service_response = ({'id': 1, 'name': 'test', 'contact_uuid': 1}, - 200, {'Content-Type': ['application/json']}) + service_response = ( + {'id': 1, 'name': 'test', 'contact_uuid': 1}, + 200, + {'Content-Type': ['application/json']}, + ) # mock second response - expand_response = ({'id': 1, 'file': '/somewhere/128/',}, - 200, {'Content-Type': ['application/json']}) + expand_response = ( + {'id': 1, 'file': '/somewhere/128/'}, + 200, + {'Content-Type': ['application/json']}, + ) mock_perform_request.side_effect = [service_response, expand_response] # make api request - path = '/{}/{}/'.format(relationship.origin_model.logic_module_endpoint_name, 'products') + path = '/{}/{}/'.format( + relationship.origin_model.logic_module_endpoint_name, 'products' + ) response = auth_api_client.get(path, {'join': ''}) # validate result @@ -34,10 +53,7 @@ def test_join_data_one_obj_w_relationships(mock_perform_request, mock_spec, auth 'id': 1, 'name': 'test', 'contact_uuid': 1, - relationship.key: [{ - 'id': 1, - 'file': '/somewhere/128/', - }] + relationship.key: [{'id': 1, 'file': '/somewhere/128/'}], } assert response.status_code == 200 @@ -47,27 +63,53 @@ def test_join_data_one_obj_w_relationships(mock_perform_request, mock_spec, auth @pytest.mark.django_db() @patch('gateway.request.GatewayRequest._get_swagger_spec') @patch('gateway.request.SwaggerClient.request') -def test_join_data_one_obj_w_two_relationships(mock_perform_request, mock_spec, auth_api_client, - relationship, relationship2): - factories.JoinRecord(relationship=relationship, record_id=1, related_record_id=2, - record_uuid=None, related_record_uuid=None) - factories.JoinRecord(relationship=relationship2, record_id=1, related_record_id=10, - record_uuid=None, related_record_uuid=None) +def test_join_data_one_obj_w_two_relationships( + mock_perform_request, mock_spec, auth_api_client, relationship, relationship2 +): + factories.JoinRecord( + relationship=relationship, + record_id=1, + related_record_id=2, + record_uuid=None, + related_record_uuid=None, + ) + factories.JoinRecord( + relationship=relationship2, + record_id=1, + related_record_id=10, + record_uuid=None, + related_record_uuid=None, + ) mock_spec.return_value = Mock(Spec) # mock first response - service_response = ({'id': 1, 'name': 'test', 'contact_uuid': 1}, - 200, {'Content-Type': ['application/json']}) + service_response = ( + {'id': 1, 'name': 'test', 'contact_uuid': 1}, + 200, + {'Content-Type': ['application/json']}, + ) # mock second response - expand_response1 = ({'id': 2, 'file': '/documents/128/'}, - 200, {'Content-Type': ['application/json']}) + expand_response1 = ( + {'id': 2, 'file': '/documents/128/'}, + 200, + {'Content-Type': ['application/json']}, + ) # mock third response - expand_response2 = ({'id': 10, 'city': 'New York'}, - 200, {'Content-Type': ['application/json']}) - mock_perform_request.side_effect = [service_response, expand_response1, expand_response2] + expand_response2 = ( + {'id': 10, 'city': 'New York'}, + 200, + {'Content-Type': ['application/json']}, + ) + mock_perform_request.side_effect = [ + service_response, + expand_response1, + expand_response2, + ] # make api request - path = '/{}/{}/'.format(relationship.origin_model.logic_module_endpoint_name, 'products') + path = '/{}/{}/'.format( + relationship.origin_model.logic_module_endpoint_name, 'products' + ) response = auth_api_client.get(path, {'join': ''}) # validate result @@ -75,14 +117,8 @@ def test_join_data_one_obj_w_two_relationships(mock_perform_request, mock_spec, 'id': 1, 'name': 'test', 'contact_uuid': 1, - relationship.key: [{ - 'id': 2, - 'file': '/documents/128/', - }], - relationship2.key: [{ - 'id': 10, - 'city': 'New York', - }] + relationship.key: [{'id': 2, 'file': '/documents/128/'}], + relationship2.key: [{'id': 10, 'city': 'New York'}], } assert response.status_code == 200 @@ -92,25 +128,37 @@ def test_join_data_one_obj_w_two_relationships(mock_perform_request, mock_spec, @pytest.mark.django_db() @patch('gateway.request.GatewayRequest._get_swagger_spec') @patch('gateway.request.SwaggerClient.request') -def test_join_data_list(mock_perform_request, mock_spec, auth_api_client, relationship_with_10_records, org): +def test_join_data_list( + mock_perform_request, mock_spec, auth_api_client, relationship_with_10_records, org +): mock_spec.return_value = Mock(Spec) join_records = relationship_with_10_records.joinrecords.all() - main_service_data = [{'uuid': item.record_uuid, 'name': f'Boiler #{i}'} - for i, item in enumerate(join_records)] - main_service_response = (main_service_data, - 200, {'Content-Type': ['application/json']}) + main_service_data = [ + {'uuid': item.record_uuid, 'name': f'Boiler #{i}'} + for i, item in enumerate(join_records) + ] + main_service_response = ( + main_service_data, + 200, + {'Content-Type': ['application/json']}, + ) expand_responses = [] for item in join_records: expand_responses.append( - ({'uuid': item.related_record_uuid, 'file': '/documents/128/'}, - 200, {'Content-Type': ['application/json']}) + ( + {'uuid': item.related_record_uuid, 'file': '/documents/128/'}, + 200, + {'Content-Type': ['application/json']}, + ) ) mock_perform_request.side_effect = [main_service_response] + expand_responses # make api request - path = '/{}/{}/'.format(relationship_with_10_records.origin_model.logic_module_endpoint_name, 'products') + path = '/{}/{}/'.format( + relationship_with_10_records.origin_model.logic_module_endpoint_name, 'products' + ) response = auth_api_client.get(path, {'join': ''}) assert response.status_code == 200 diff --git a/datamesh/tests/test_models.py b/datamesh/tests/test_models.py index b3f5b945..b79b23b9 100644 --- a/datamesh/tests/test_models.py +++ b/datamesh/tests/test_models.py @@ -6,7 +6,11 @@ from datamesh.models import JoinRecord, Relationship, LogicModuleModel from core.tests.fixtures import org -from .fixtures import relationship, appointment_logic_module_model, document_logic_module_model +from .fixtures import ( + relationship, + appointment_logic_module_model, + document_logic_module_model, +) @pytest.mark.django_db() @@ -14,12 +18,14 @@ def test_fail_create_reverse_relationship(relationship): with pytest.raises(ValidationError): Relationship.objects.create( related_model=relationship.origin_model, - origin_model=relationship.related_model + origin_model=relationship.related_model, ) @pytest.mark.django_db() -def test_get_by_concatenated_model_name(appointment_logic_module_model, document_logic_module_model): +def test_get_by_concatenated_model_name( + appointment_logic_module_model, document_logic_module_model +): lmm = LogicModuleModel.objects.get_by_concatenated_model_name("crmAppointment") assert lmm == appointment_logic_module_model assert None == LogicModuleModel.objects.get_by_concatenated_model_name("nothing") @@ -28,10 +34,7 @@ def test_get_by_concatenated_model_name(appointment_logic_module_model, document @pytest.mark.django_db() def test_create_join_record(relationship, org): JoinRecord.objects.create( - relationship=relationship, - record_id=1, - related_record_id=2, - organization=org, + relationship=relationship, record_id=1, related_record_id=2, organization=org ) JoinRecord.objects.create( relationship=relationship, @@ -43,44 +46,45 @@ def test_create_join_record(relationship, org): @pytest.mark.django_db() -def test_one_record_primary_key_check_constraint_fail_empty_record_id_and_record_uuid(relationship, org): +def test_one_record_primary_key_check_constraint_fail_empty_record_id_and_record_uuid( + relationship, org +): with pytest.raises(IntegrityError): with transaction.atomic(): JoinRecord.objects.create( - related_record_id=1, - relationship=relationship, - organization=org, + related_record_id=1, relationship=relationship, organization=org ) assert JoinRecord.objects.count() == 0 @pytest.mark.django_db() -def test_one_record_primary_key_check_constraint_fail_filled_id_and_uuid(relationship, org): +def test_one_record_primary_key_check_constraint_fail_filled_id_and_uuid( + relationship, org +): with pytest.raises(IntegrityError): with transaction.atomic(): JoinRecord.objects.create( - record_id=1, - record_uuid=1, - relationship=relationship, - organization=org, + record_id=1, record_uuid=1, relationship=relationship, organization=org ) assert JoinRecord.objects.count() == 0 @pytest.mark.django_db() -def test_one_record_primary_key_check_constraint_fail_empty_related_id_and_related_uuid(relationship, org): +def test_one_record_primary_key_check_constraint_fail_empty_related_id_and_related_uuid( + relationship, org +): with pytest.raises(IntegrityError): with transaction.atomic(): JoinRecord.objects.create( - record_id=1, - relationship=relationship, - organization=org, + record_id=1, relationship=relationship, organization=org ) assert JoinRecord.objects.count() == 0 @pytest.mark.django_db() -def test_one_record_primary_key_check_constraint_fail_filled_related_id_and_related_uuid(relationship, org): +def test_one_record_primary_key_check_constraint_fail_filled_related_id_and_related_uuid( + relationship, org +): with pytest.raises(IntegrityError): with transaction.atomic(): JoinRecord.objects.create( @@ -95,16 +99,12 @@ def test_one_record_primary_key_check_constraint_fail_filled_related_id_and_rela @pytest.mark.django_db() def test_unique_together_join_record_id_id(relationship, org): JoinRecord.objects.create( - record_id=1, - related_record_id=1, - relationship=relationship, + record_id=1, related_record_id=1, relationship=relationship ) with pytest.raises(IntegrityError): with transaction.atomic(): JoinRecord.objects.create( - record_id=1, - related_record_id=1, - relationship=relationship, + record_id=1, related_record_id=1, relationship=relationship ) assert JoinRecord.objects.count() == 1 @@ -112,16 +112,12 @@ def test_unique_together_join_record_id_id(relationship, org): @pytest.mark.django_db() def test_unique_together_join_record_uuid_uuid(relationship, org): JoinRecord.objects.create( - record_uuid=1, - related_record_uuid=1, - relationship=relationship, + record_uuid=1, related_record_uuid=1, relationship=relationship ) with pytest.raises(IntegrityError): with transaction.atomic(): JoinRecord.objects.create( - record_uuid=1, - related_record_uuid=1, - relationship=relationship, + record_uuid=1, related_record_uuid=1, relationship=relationship ) assert JoinRecord.objects.count() == 1 @@ -129,16 +125,12 @@ def test_unique_together_join_record_uuid_uuid(relationship, org): @pytest.mark.django_db() def test_unique_together_join_record_id_uuid(relationship, org): JoinRecord.objects.create( - record_id=1, - related_record_uuid=1, - relationship=relationship, + record_id=1, related_record_uuid=1, relationship=relationship ) with pytest.raises(IntegrityError): with transaction.atomic(): JoinRecord.objects.create( - record_id=1, - related_record_uuid=1, - relationship=relationship, + record_id=1, related_record_uuid=1, relationship=relationship ) assert JoinRecord.objects.count() == 1 @@ -146,15 +138,11 @@ def test_unique_together_join_record_id_uuid(relationship, org): @pytest.mark.django_db() def test_unique_together_join_record_uuid_id(relationship, org): JoinRecord.objects.create( - record_uuid=1, - related_record_id=1, - relationship=relationship, + record_uuid=1, related_record_id=1, relationship=relationship ) with pytest.raises(IntegrityError): with transaction.atomic(): JoinRecord.objects.create( - record_uuid=1, - related_record_id=1, - relationship=relationship, + record_uuid=1, related_record_id=1, relationship=relationship ) assert JoinRecord.objects.count() == 1 diff --git a/datamesh/tests/test_serializers.py b/datamesh/tests/test_serializers.py index 70c50964..69bf15ae 100644 --- a/datamesh/tests/test_serializers.py +++ b/datamesh/tests/test_serializers.py @@ -7,10 +7,11 @@ def test_join_record_serializer_from_instance(request_factory, join_record): request = request_factory.get('') request.session = { - 'jwt_organization_uuid': join_record.organization.organization_uuid, + 'jwt_organization_uuid': join_record.organization.organization_uuid } - serializer = JoinRecordSerializer(instance=join_record, - context={'request': request}) + serializer = JoinRecordSerializer( + instance=join_record, context={'request': request} + ) keys = [ "join_record_uuid", "record_id", diff --git a/datamesh/tests/test_views.py b/datamesh/tests/test_views.py index 17f09497..99d7c327 100644 --- a/datamesh/tests/test_views.py +++ b/datamesh/tests/test_views.py @@ -14,7 +14,7 @@ appointment_logic_module_model, join_record, relationship, - relationship2 + relationship2, ) @pytest.mark.django_db() @@ -36,7 +36,7 @@ def test_join_record_list_view_minimal( ): join_records = factories.JoinRecord.create_batch( size=5, - **{"organization__organization_uuid": TEST_USER_DATA["organization_uuid"]} + **{"organization__organization_uuid": TEST_USER_DATA["organization_uuid"]}, ) request = request_factory.get("") request.user = org_admin @@ -44,8 +44,9 @@ def test_join_record_list_view_minimal( response = views.JoinRecordViewSet.as_view({"get": "list"})(request) assert response.status_code == 200 assert len(response.data) == 5 - assert set([str(jr.join_record_uuid) for jr in join_records]) == \ - set([jr['join_record_uuid'] for jr in response.data]) + assert set([str(jr.join_record_uuid) for jr in join_records]) == set( + [jr['join_record_uuid'] for jr in response.data] + ) def test_join_record_list_view_organization_only( self, @@ -58,12 +59,12 @@ def test_join_record_list_view_organization_only( ): join_records = factories.JoinRecord.create_batch( size=5, - **{"organization__organization_uuid": TEST_USER_DATA["organization_uuid"]} + **{"organization__organization_uuid": TEST_USER_DATA["organization_uuid"]}, ) - factories.JoinRecord.create(organization=factories.Organization( - name='Another Organization' - )) + factories.JoinRecord.create( + organization=factories.Organization(name='Another Organization') + ) request = request_factory.get("") request.user = org_admin @@ -76,11 +77,17 @@ def test_join_record_list_view_organization_only( ) def test_join_record_detail_fail_organization_permission( - self, request_factory, org_admin, document_logic_module, - crm_logic_module, document_logic_module_model, appointment_logic_module_model,): - join_record = factories.JoinRecord.create(organization=factories.Organization( - name='Another Organization' - )) + self, + request_factory, + org_admin, + document_logic_module, + crm_logic_module, + document_logic_module_model, + appointment_logic_module_model, + ): + join_record = factories.JoinRecord.create( + organization=factories.Organization(name='Another Organization') + ) request = request_factory.get(str(join_record.join_record_uuid)) request.user = org_admin request.session = self.session @@ -88,17 +95,17 @@ def test_join_record_detail_fail_organization_permission( assert response.status_code == 200 def test_join_record_list_filter_one_record_id( - self, - request_factory, - org_admin, - document_logic_module, - crm_logic_module, - document_logic_module_model, - appointment_logic_module_model, + self, + request_factory, + org_admin, + document_logic_module, + crm_logic_module, + document_logic_module_model, + appointment_logic_module_model, ): join_records = factories.JoinRecord.create_batch( size=5, - **{"organization__organization_uuid": TEST_USER_DATA["organization_uuid"]} + **{"organization__organization_uuid": TEST_USER_DATA["organization_uuid"]}, ) query_params = { 'related_record_uuid': join_records[0].related_record_uuid, @@ -110,23 +117,26 @@ def test_join_record_list_filter_one_record_id( response = views.JoinRecordViewSet.as_view({"get": "list"})(request) assert response.status_code == 200 assert len(response.data) == 1 - assert str(join_records[0].join_record_uuid) == response.data[0]["join_record_uuid"] + assert ( + str(join_records[0].join_record_uuid) + == response.data[0]["join_record_uuid"] + ) def test_join_record_list_filter_several_record_uuids( - self, - request_factory, - org_admin, - document_logic_module, - crm_logic_module, - document_logic_module_model, - appointment_logic_module_model, + self, + request_factory, + org_admin, + document_logic_module, + crm_logic_module, + document_logic_module_model, + appointment_logic_module_model, ): join_records = factories.JoinRecord.create_batch( size=5, - **{"organization__organization_uuid": TEST_USER_DATA["organization_uuid"]} + **{"organization__organization_uuid": TEST_USER_DATA["organization_uuid"]}, ) query_params = { - 'related_record_uuid': f'{join_records[0].related_record_uuid},{join_records[1].related_record_uuid}', + 'related_record_uuid': f'{join_records[0].related_record_uuid},{join_records[1].related_record_uuid}' } request = request_factory.get(f'?{urlencode(query_params)}') request.user = org_admin @@ -134,8 +144,12 @@ def test_join_record_list_filter_several_record_uuids( response = views.JoinRecordViewSet.as_view({"get": "list"})(request) assert response.status_code == 200 assert len(response.data) == 2 - assert set((str(join_records[0].join_record_uuid), str(join_records[1].join_record_uuid))) == set( - [jr["join_record_uuid"] for jr in response.data]) + assert set( + ( + str(join_records[0].join_record_uuid), + str(join_records[1].join_record_uuid), + ) + ) == set([jr["join_record_uuid"] for jr in response.data]) @pytest.mark.django_db() @@ -169,8 +183,12 @@ def test_join_record_create_view( def test_join_record_detail_view(request_factory, join_record, org_admin): request = request_factory.get("") request.user = org_admin - request.session = {"jwt_organization_uuid": str(join_record.organization.organization_uuid)} - response = views.JoinRecordViewSet.as_view({"get": "retrieve"})(request, pk=str(join_record.pk)) + request.session = { + "jwt_organization_uuid": str(join_record.organization.organization_uuid) + } + response = views.JoinRecordViewSet.as_view({"get": "retrieve"})( + request, pk=str(join_record.pk) + ) assert response.status_code == 200 assert str(join_record.pk) == response.data["join_record_uuid"] @@ -180,7 +198,9 @@ def test_join_record_detail_view_no_access(request_factory, join_record, org_adm request = request_factory.get("") request.user = org_admin request.session = {"jwt_organization_uuid": str(uuid.uuid4())} - response = views.JoinRecordViewSet.as_view({"get": "retrieve"})(request, pk=str(join_record.pk)) + response = views.JoinRecordViewSet.as_view({"get": "retrieve"})( + request, pk=str(join_record.pk) + ) assert response.status_code == 404 @@ -188,18 +208,20 @@ def test_join_record_detail_view_no_access(request_factory, join_record, org_adm class TestLogicModuleModelView: expected_keys = { - 'logic_module_model_uuid', - 'logic_module_endpoint_name', - 'model', - 'endpoint', - 'lookup_field_name', - 'is_local', - } - - def test_list_logic_module_models(self, - request_factory, - document_logic_module_model, - appointment_logic_module_model): + 'logic_module_model_uuid', + 'logic_module_endpoint_name', + 'model', + 'endpoint', + 'lookup_field_name', + 'is_local', + } + + def test_list_logic_module_models( + self, + request_factory, + document_logic_module_model, + appointment_logic_module_model, + ): request = request_factory.get(reverse('logicmodulemodel-list')) user = factories.CoreUser() request.user = user @@ -213,7 +235,7 @@ def test_create_logic_module_model(self, request_factory): "logic_module_endpoint_name": "location", "model": "siteprofile", "endpoint": "/siteprofiles/", - "lookup_field_name": "uuid" + "lookup_field_name": "uuid", } request = request_factory.post(reverse('logicmodulemodel-list'), data) user = factories.CoreUser(is_superuser=True) @@ -227,7 +249,7 @@ def test_create_logic_module_model_no_access(self, request_factory): "logic_module_endpoint_name": "location", "model": "siteprofile", "endpoint": "/siteprofiles/", - "lookup_field_name": "uuid" + "lookup_field_name": "uuid", } request = request_factory.post(reverse('logicmodulemodel-list'), data) @@ -236,72 +258,100 @@ def test_create_logic_module_model_no_access(self, request_factory): response = views.LogicModuleModelViewSet.as_view({"post": "create"})(request) assert response.status_code == 403 - def test_detail_logic_module_models(self, request_factory, document_logic_module_model): + def test_detail_logic_module_models( + self, request_factory, document_logic_module_model + ): pk = str(document_logic_module_model.pk) request = request_factory.get(reverse('logicmodulemodel-detail', args=(pk,))) user = factories.CoreUser() request.user = user - response = views.LogicModuleModelViewSet.as_view({"get": "retrieve"})(request, pk=pk) + response = views.LogicModuleModelViewSet.as_view({"get": "retrieve"})( + request, pk=pk + ) assert response.status_code == 200 assert self.expected_keys == set(response.data.keys()) - def test_update_logic_module_model(self, request_factory, document_logic_module_model): + def test_update_logic_module_model( + self, request_factory, document_logic_module_model + ): pk = str(document_logic_module_model.pk) data = { "logic_module_endpoint_name": "document", "model": "document", "endpoint": "/documents/", - "lookup_field_name": "another_lookup_field" + "lookup_field_name": "another_lookup_field", } - request = request_factory.put(reverse('logicmodulemodel-detail', args=(pk,)), data) + request = request_factory.put( + reverse('logicmodulemodel-detail', args=(pk,)), data + ) user = factories.CoreUser(is_superuser=True) request.user = user - response = views.LogicModuleModelViewSet.as_view({"put": "update"})(request, pk=pk) + response = views.LogicModuleModelViewSet.as_view({"put": "update"})( + request, pk=pk + ) assert response.status_code == 200 assert self.expected_keys == set(response.data.keys()) assert response.data['lookup_field_name'] == 'another_lookup_field' - def test_update_logic_module_model_no_access(self, request_factory, document_logic_module_model): + def test_update_logic_module_model_no_access( + self, request_factory, document_logic_module_model + ): pk = str(document_logic_module_model.pk) data = { "logic_module_endpoint_name": "document", "model": "document", "endpoint": "/documents/", - "lookup_field_name": "another_lookup_field" + "lookup_field_name": "another_lookup_field", } - request = request_factory.put(reverse('logicmodulemodel-detail', args=(pk,)), data) + request = request_factory.put( + reverse('logicmodulemodel-detail', args=(pk,)), data + ) user = factories.CoreUser() request.user = user - response = views.LogicModuleModelViewSet.as_view({"put": "update"})(request, pk=pk) + response = views.LogicModuleModelViewSet.as_view({"put": "update"})( + request, pk=pk + ) assert response.status_code == 403 - def test_patch_logic_module_model(self, request_factory, document_logic_module_model): + def test_patch_logic_module_model( + self, request_factory, document_logic_module_model + ): pk = str(document_logic_module_model.pk) - data = { - "lookup_field_name": "another_lookup_field" - } - request = request_factory.patch(reverse('logicmodulemodel-detail', args=(pk,)), data) + data = {"lookup_field_name": "another_lookup_field"} + request = request_factory.patch( + reverse('logicmodulemodel-detail', args=(pk,)), data + ) user = factories.CoreUser(is_superuser=True) request.user = user - response = views.LogicModuleModelViewSet.as_view({"patch": "partial_update"})(request, pk=pk) + response = views.LogicModuleModelViewSet.as_view({"patch": "partial_update"})( + request, pk=pk + ) assert response.status_code == 200 assert self.expected_keys == set(response.data.keys()) assert response.data['lookup_field_name'] == 'another_lookup_field' - def test_delete_logic_module_models(self, request_factory, document_logic_module_model): + def test_delete_logic_module_models( + self, request_factory, document_logic_module_model + ): pk = str(document_logic_module_model.pk) request = request_factory.delete(reverse('logicmodulemodel-detail', args=(pk,))) user = factories.CoreUser(is_superuser=True) request.user = user - response = views.LogicModuleModelViewSet.as_view({"delete": "destroy"})(request, pk=pk) + response = views.LogicModuleModelViewSet.as_view({"delete": "destroy"})( + request, pk=pk + ) assert response.status_code == 204 - def test_delete_logic_module_models_no_access(self, request_factory, document_logic_module_model): + def test_delete_logic_module_models_no_access( + self, request_factory, document_logic_module_model + ): pk = str(document_logic_module_model.pk) request = request_factory.delete(reverse('logicmodulemodel-detail', args=(pk,))) user = factories.CoreUser() request.user = user - response = views.LogicModuleModelViewSet.as_view({"delete": "destroy"})(request, pk=pk) + response = views.LogicModuleModelViewSet.as_view({"delete": "destroy"})( + request, pk=pk + ) assert response.status_code == 403 @@ -324,17 +374,25 @@ def test_list_relationships(self, request_factory, relationship, relationship2): assert response.status_code == 200 assert len(response.data) == 2 assert set(response.data[0].keys()) == self.expected_keys - assert set(response.data[0]['origin_model'].keys()) == TestLogicModuleModelView.expected_keys - assert set(response.data[0]['related_model'].keys()) == TestLogicModuleModelView.expected_keys - - def test_create_relationship(self, - request_factory, - document_logic_module_model, - appointment_logic_module_model): - data= { + assert ( + set(response.data[0]['origin_model'].keys()) + == TestLogicModuleModelView.expected_keys + ) + assert ( + set(response.data[0]['related_model'].keys()) + == TestLogicModuleModelView.expected_keys + ) + + def test_create_relationship( + self, + request_factory, + document_logic_module_model, + appointment_logic_module_model, + ): + data = { "origin_model_id": document_logic_module_model.pk, "related_model_id": appointment_logic_module_model.pk, - "key": "document_appointment_rel" + "key": "document_appointment_rel", } request = request_factory.post(reverse('relationship-list'), data) user = factories.CoreUser(is_superuser=True) @@ -343,14 +401,16 @@ def test_create_relationship(self, assert response.status_code == 201 assert self.expected_keys == set(response.data.keys()) - def test_create_relationship_no_access(self, - request_factory, - document_logic_module_model, - appointment_logic_module_model): - data= { + def test_create_relationship_no_access( + self, + request_factory, + document_logic_module_model, + appointment_logic_module_model, + ): + data = { "origin_model_id": document_logic_module_model.pk, "related_model_id": appointment_logic_module_model.pk, - "key": "document_appointment_rel" + "key": "document_appointment_rel", } request = request_factory.post(reverse('relationship-list'), data) user = factories.CoreUser() @@ -363,7 +423,9 @@ def test_detail_relationship(self, request_factory, relationship): request = request_factory.get(reverse('relationship-detail', args=(pk,))) user = factories.CoreUser() request.user = user - response = views.RelationshiplViewSet.as_view({"get": "retrieve"})(request, pk=pk) + response = views.RelationshiplViewSet.as_view({"get": "retrieve"})( + request, pk=pk + ) assert response.status_code == 200 assert self.expected_keys == set(response.data.keys()) @@ -372,7 +434,7 @@ def test_update_relationship(self, request_factory, relationship): data = { "origin_model_id": relationship.origin_model.pk, "related_model_id": relationship.related_model.pk, - "key": "another_key_for_this_rel" + "key": "another_key_for_this_rel", } request = request_factory.put(reverse('relationship-detail', args=(pk,)), data) user = factories.CoreUser(is_superuser=True) @@ -387,7 +449,7 @@ def test_update_relationship_no_access(self, request_factory, relationship): data = { "origin_model_id": relationship.origin_model.pk, "related_model_id": relationship.related_model.pk, - "key": "another_key_for_this_rel" + "key": "another_key_for_this_rel", } request = request_factory.put(reverse('relationship-detail', args=(pk,)), data) user = factories.CoreUser() @@ -397,13 +459,15 @@ def test_update_relationship_no_access(self, request_factory, relationship): def test_patch_relationship(self, request_factory, relationship): pk = str(relationship.pk) - data = { - "key": "another_key_for_this_rel" - } - request = request_factory.patch(reverse('relationship-detail', args=(pk,)), data) + data = {"key": "another_key_for_this_rel"} + request = request_factory.patch( + reverse('relationship-detail', args=(pk,)), data + ) user = factories.CoreUser(is_superuser=True) request.user = user - response = views.RelationshiplViewSet.as_view({"patch": "partial_update"})(request, pk=pk) + response = views.RelationshiplViewSet.as_view({"patch": "partial_update"})( + request, pk=pk + ) assert response.status_code == 200 assert self.expected_keys == set(response.data.keys()) assert response.data['key'] == 'another_key_for_this_rel' @@ -413,7 +477,9 @@ def test_delete_relationship(self, request_factory, relationship): request = request_factory.delete(reverse('relationship-detail', args=(pk,))) user = factories.CoreUser(is_superuser=True) request.user = user - response = views.RelationshiplViewSet.as_view({"delete": "destroy"})(request, pk=pk) + response = views.RelationshiplViewSet.as_view({"delete": "destroy"})( + request, pk=pk + ) assert response.status_code == 204 def test_delete_relationship_no_access(self, request_factory, relationship): @@ -421,5 +487,7 @@ def test_delete_relationship_no_access(self, request_factory, relationship): request = request_factory.delete(reverse('relationship-detail', args=(pk,))) user = factories.CoreUser() request.user = user - response = views.RelationshiplViewSet.as_view({"delete": "destroy"})(request, pk=pk) + response = views.RelationshiplViewSet.as_view({"delete": "destroy"})( + request, pk=pk + ) assert response.status_code == 403 diff --git a/datamesh/utils.py b/datamesh/utils.py index 290830fc..78dedddd 100644 --- a/datamesh/utils.py +++ b/datamesh/utils.py @@ -7,14 +7,17 @@ from gateway.utils import valid_uuid4 -def prepare_lookup_kwargs(is_forward_lookup: bool, - relationship: Relationship, - join_record: JoinRecord) -> Tuple[LogicModuleModel, str]: +def prepare_lookup_kwargs( + is_forward_lookup: bool, relationship: Relationship, join_record: JoinRecord +) -> Tuple[LogicModuleModel, str]: """Find out if pk is id or uuid and prepare lookup according to direction.""" if is_forward_lookup: related_model = relationship.related_model - related_record_field = 'related_record_id' if join_record.related_record_id is not None \ + related_record_field = ( + 'related_record_id' + if join_record.related_record_id is not None else 'related_record_uuid' + ) else: related_model = relationship.origin_model related_record_field = 'record_id' if join_record.record_id is not None \ diff --git a/datamesh/views.py b/datamesh/views.py index 737b7bed..eb62a093 100644 --- a/datamesh/views.py +++ b/datamesh/views.py @@ -4,7 +4,11 @@ from .filters import JoinRecordFilter from .mixins import OrganizationQuerySetMixin from .models import JoinRecord, LogicModuleModel, Relationship -from .serializers import JoinRecordSerializer, LogicModuleModelSerializer, RelationshipSerializer +from .serializers import ( + JoinRecordSerializer, + LogicModuleModelSerializer, + RelationshipSerializer, +) from workflow.permissions import IsSuperUserOrReadOnly @@ -20,15 +24,16 @@ class RelationshiplViewSet(viewsets.ModelViewSet): permission_classes = (IsSuperUserOrReadOnly,) -class JoinRecordViewSet(OrganizationQuerySetMixin, - viewsets.ModelViewSet): +class JoinRecordViewSet(OrganizationQuerySetMixin, viewsets.ModelViewSet): queryset = JoinRecord.objects.all() serializer_class = JoinRecordSerializer filter_backends = (DjangoFilterBackend,) filter_class = JoinRecordFilter - filter_fields = ('relationship__key', - 'record_id', - 'record_uuid', - 'related_record_id', - 'related_record_uuid',) + filter_fields = ( + 'relationship__key', + 'record_id', + 'record_uuid', + 'related_record_id', + 'related_record_uuid', + ) diff --git a/docker-compose.yml b/docker-compose.yml index 0893a477..84df5db9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -73,11 +73,11 @@ services: EMAIL_HOST_PASSWORD: "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" EMAIL_PORT: "587" EMAIL_USE_TLS: "False" - EMAIL_SUBJECT_PREFIX: "NO REPLY: Transparent Path - " + EMAIL_SUBJECT_PREFIX: "NO REPLY: " EMAIL_BACKEND: "SMTP" DEFAULT_FROM_EMAIL: "admin@buildly.io" RESETPASS_CONFIRM_URL_PATH: "reset-password-confirm/" - STRIPE_SECRET: "sk_test_ddLjeqLRWcj8znVAhMaNPu7J00evbJrikA" + STRIPE_SECRET: "" volumes: static-content: diff --git a/docs/conf.py b/docs/conf.py index e685783f..aec4c1fe 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -109,15 +109,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -127,8 +124,13 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'BuildlyCore.tex', u'Buildly Core Documentation', - u'Buildly.io', 'manual'), + ( + master_doc, + 'BuildlyCore.tex', + u'Buildly Core Documentation', + u'Buildly.io', + 'manual', + ) ] @@ -136,10 +138,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'buildlycore', u'Buildly Core Documentation', - [author], 1) -] +man_pages = [(master_doc, 'buildlycore', u'Buildly Core Documentation', [author], 1)] # -- Options for Texinfo output ---------------------------------------------- @@ -148,9 +147,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'BuildlyCore', u'Buildly Core Documentation', - author, 'BuildlyCore', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + 'BuildlyCore', + u'Buildly Core Documentation', + author, + 'BuildlyCore', + 'One line description of project.', + 'Miscellaneous', + ) ] diff --git a/factories/core_models.py b/factories/core_models.py index 3aae9e14..b2f25897 100644 --- a/factories/core_models.py +++ b/factories/core_models.py @@ -5,7 +5,7 @@ CoreUser as CoreUserM, CoreGroup as CoreGroupM, LogicModule as LogicModuleM, - Organization as OrganizationM + Organization as OrganizationM, ) diff --git a/factories/datamesh_models.py b/factories/datamesh_models.py index d67f4ce9..1ce8badb 100644 --- a/factories/datamesh_models.py +++ b/factories/datamesh_models.py @@ -4,15 +4,18 @@ from factory import DjangoModelFactory, SubFactory, LazyAttribute -from datamesh.models import (LogicModuleModel as LogicModulModelM, - Relationship as RelationshipM, - JoinRecord as JoinRecordM) +from datamesh.models import ( + LogicModuleModel as LogicModulModelM, + Relationship as RelationshipM, + JoinRecord as JoinRecordM, +) from factories import Organization class LogicModuleModel(DjangoModelFactory): logic_module_endpoint_name = LazyAttribute( - lambda o: ''.join(random.choices(string.ascii_uppercase + string.digits, k=16))) + lambda o: ''.join(random.choices(string.ascii_uppercase + string.digits, k=16)) + ) lookup_field_name = 'id' class Meta: diff --git a/factories/oauth2_models.py b/factories/oauth2_models.py index b4d3db89..1d6fadab 100644 --- a/factories/oauth2_models.py +++ b/factories/oauth2_models.py @@ -4,7 +4,8 @@ from oauth2_provider.models import ( Application as ApplicationM, AccessToken as AccessTokenM, - RefreshToken as RefreshTokenM) + RefreshToken as RefreshTokenM, +) from .workflow_models import CoreUser diff --git a/factories/workflow_models.py b/factories/workflow_models.py index b3f0071d..b989b054 100644 --- a/factories/workflow_models.py +++ b/factories/workflow_models.py @@ -7,7 +7,8 @@ WorkflowLevel2Sort as WorkflowLevel2SortM, Internationalization as InternationalizationM, WorkflowLevelType as WorkflowLevelTypeM, - WorkflowLevelStatus as WorkflowLevelStatusM) + WorkflowLevelStatus as WorkflowLevelStatusM, +) from .django_models import Group from .core_models import CoreUser, Organization diff --git a/gateway/aggregator.py b/gateway/aggregator.py index f74e7329..0fb723d7 100644 --- a/gateway/aggregator.py +++ b/gateway/aggregator.py @@ -28,19 +28,25 @@ def get_aggregate_swagger(self) -> dict: # Get the swagger.json try: # Use stored specification of the module - spec_dict = LogicModule.objects.values_list( - 'api_specification', flat=True).filter(endpoint_name=endpoint_name).first() + spec_dict = ( + LogicModule.objects.values_list( + 'api_specification', flat=True + ) + .filter(endpoint_name=endpoint_name) + .first() + ) # Pull specification of the module from its service and store it if spec_dict is None: response = utils.get_swagger_from_url(schema_url) spec_dict = response.json() - LogicModule.objects.filter(endpoint_name=endpoint_name).update( - api_specification=spec_dict) + LogicModule.objects.filter( + endpoint_name=endpoint_name + ).update(api_specification=spec_dict) swagger_apis[endpoint_name] = { 'spec': spec_dict, - 'url': schema_url + 'url': schema_url, } except ConnectionError as error: logger.warning(error) @@ -50,8 +56,7 @@ def get_aggregate_swagger(self) -> dict: logger.info('Cannot remove {} from errors'.format(schema_url)) return swagger_apis - def _update_specification(self, name: str, api_name: str, - api_spec: dict) -> dict: + def _update_specification(self, name: str, api_name: str, api_spec: dict) -> dict: """ Update names of the specification of a service's API @@ -83,19 +88,23 @@ def merge_aggregates(self) -> dict: 'consumes': self.configuration.get('consumes', None), 'produces': self.configuration.get('produces', None), 'definitions': {}, - 'paths': {} + 'paths': {}, } swagger_apis = self.get_aggregate_swagger() for api, api_spec in swagger_apis.items(): # Rename definition to avoid collision. - api_spec['spec'] = json.loads(json.dumps(api_spec['spec']).replace( - '#/definitions/', u'#/definitions/{}'.format(api))) + api_spec['spec'] = json.loads( + json.dumps(api_spec['spec']).replace( + '#/definitions/', u'#/definitions/{}'.format(api) + ) + ) # update the definitions if 'definitions' in api_spec['spec']: - basic_swagger['definitions'].update(self._update_specification( - 'definitions', api, api_spec)) + basic_swagger['definitions'].update( + self._update_specification('definitions', api, api_spec) + ) # update the paths to match with the gateway if 'paths' in api_spec['spec']: @@ -103,8 +112,9 @@ def merge_aggregates(self) -> dict: basic_swagger['paths'].update(api_spec['spec']['paths']) else: api_name = '/{}'.format(api) - basic_swagger['paths'].update(self._update_specification( - 'paths', api_name, api_spec)) + basic_swagger['paths'].update( + self._update_specification('paths', api_name, api_spec) + ) return basic_swagger @@ -121,7 +131,8 @@ def generate_operation_id(self, swagger): if 'operationId' in action_spec: current_op_id = action_spec['operationId'] action_spec['operationId'] = '{}.{}'.format( - service_name, current_op_id) + service_name, current_op_id + ) def generate_swagger(self): """ diff --git a/gateway/clients.py b/gateway/clients.py index 5e936eed..747cb4ac 100644 --- a/gateway/clients.py +++ b/gateway/clients.py @@ -28,7 +28,10 @@ def request(self, **kwargs): def is_valid_for_cache(self) -> bool: """ Checks if request is valid for caching operations """ - return self._in_request.method.lower() == 'get' and not self._in_request.query_params + return ( + self._in_request.method.lower() == 'get' + and not self._in_request.query_params + ) def prepare_data(self, spec: Spec, **kwargs) -> Tuple[str, str]: """ Parse request URL, validates operation, and returns method and URL for outgoing request""" @@ -87,17 +90,21 @@ def get_request_data(self) -> dict: data.pop('aggregate', None) data.pop('join', None) - query_dict_body = self._in_request.data if hasattr(self._in_request, 'data') else dict() - body = query_dict_body.dict() if isinstance(query_dict_body, QueryDict) else query_dict_body + query_dict_body = ( + self._in_request.data if hasattr(self._in_request, 'data') else dict() + ) + body = ( + query_dict_body.dict() + if isinstance(query_dict_body, QueryDict) + else query_dict_body + ) data.update(body) # handle uploaded files if self._in_request.FILES: for key, value in self._in_request.FILES.items(): data[key] = { - 'header': { - 'Content-Type': value.content_type, - }, + 'header': {'Content-Type': value.content_type}, 'data': value, 'filename': value.name, } @@ -107,7 +114,7 @@ def get_request_data(self) -> dict: def get_headers(self) -> dict: """Get data and headers from the incoming request.""" headers = { - 'Authorization': get_authorization_header(self._in_request).decode('utf-8'), + 'Authorization': get_authorization_header(self._in_request).decode('utf-8') } if self._in_request.content_type == 'application/json': headers['content-type'] = 'application/json' @@ -132,15 +139,19 @@ def request(self, **kwargs) -> Tuple[Any, int, Dict[str, str]]: # Make request to the service method = getattr(requests, method) try: - response = method(url, - headers=self.get_headers(), - params=self._in_request.query_params, - data=self.get_request_data(), - files=self._in_request.FILES) + response = method( + url, + headers=self.get_headers(), + params=self._in_request.query_params, + data=self.get_request_data(), + files=self._in_request.FILES, + ) except Exception as e: - error_msg = (f'An error occurred when redirecting the request to ' - f'or receiving the response from the service.\n' - f'Origin: ({e.__class__.__name__}: {e})') + error_msg = ( + f'An error occurred when redirecting the request to ' + f'or receiving the response from the service.\n' + f'Origin: ({e.__class__.__name__}: {e})' + ) raise exceptions.GatewayError(error_msg) try: diff --git a/gateway/generator.py b/gateway/generator.py index b2a7c4c5..fe5e4d76 100644 --- a/gateway/generator.py +++ b/gateway/generator.py @@ -9,18 +9,18 @@ class OpenAPISchemaGenerator(drf_gen.OpenAPISchemaGenerator): def get_schema(self, request=None, public=False): schema_urls = utils.get_swagger_urls() config_aggregator = { - 'info': { - 'title': 'API Gateway', - 'description': '', - 'version': '1.0' - }, + 'info': {'title': 'API Gateway', 'description': '', 'version': '1.0'}, 'apis': schema_urls, - 'produces': ['application/json', - 'application/x-www-form-urlencoded', - 'multipart/form-data'], - 'consumes': ['application/json', - 'application/x-www-form-urlencoded', - 'multipart/form-data'], + 'produces': [ + 'application/json', + 'application/x-www-form-urlencoded', + 'multipart/form-data', + ], + 'consumes': [ + 'application/json', + 'application/x-www-form-urlencoded', + 'multipart/form-data', + ], } sw_aggregator = SwaggerAggregator(config_aggregator) swagger_spec = sw_aggregator.generate_swagger() @@ -40,8 +40,7 @@ def get_schema(self, request=None, public=False): paths=paths, consumes=swagger_spec['consumes'], produces=swagger_spec['produces'], - security_definitions=swagger_spec.get( - 'security_definitions', None), + security_definitions=swagger_spec.get('security_definitions', None), security=swagger_spec.get('security', None), _url=url, _version=self.version, diff --git a/gateway/permissions.py b/gateway/permissions.py index 2bb27951..6d8861b8 100644 --- a/gateway/permissions.py +++ b/gateway/permissions.py @@ -15,7 +15,9 @@ class AllowLogicModuleGroup(permissions.BasePermission): @staticmethod def _get_logic_module(service_name: str) -> LogicModule: try: - return LogicModule.objects.prefetch_related('core_groups').get(endpoint_name=service_name) + return LogicModule.objects.prefetch_related('core_groups').get( + endpoint_name=service_name + ) except LogicModule.DoesNotExist: raise ServiceDoesNotExist(f'Service "{service_name}" not found.') @@ -28,19 +30,31 @@ def has_permission(self, request, view): service_name = view.kwargs['service'] logic_module = self._get_logic_module(service_name=service_name) - logic_module_group = logic_module.core_groups.filter(Q(is_global=True) | - Q(organization=request.user.organization, - is_global=False, is_org_level=True)) + logic_module_group = logic_module.core_groups.filter( + Q(is_global=True) + | Q( + organization=request.user.organization, + is_global=False, + is_org_level=True, + ) + ) if logic_module_group: # default permission is no access '0000' viewonly_display_permissions = '{0:04b}'.format(PERMISSIONS_NO_ACCESS) - global_permissions, org_permissions = viewonly_display_permissions, viewonly_display_permissions + global_permissions, org_permissions = ( + viewonly_display_permissions, + viewonly_display_permissions, + ) for group in logic_module_group: if group.is_global: - global_permissions = merge_permissions(global_permissions, group.display_permissions) + global_permissions = merge_permissions( + global_permissions, group.display_permissions + ) elif group.is_org_level: - org_permissions = merge_permissions(org_permissions, group.display_permissions) + org_permissions = merge_permissions( + org_permissions, group.display_permissions + ) method = request.META['REQUEST_METHOD'] if has_permission(global_permissions, method): diff --git a/gateway/request.py b/gateway/request.py index a7652553..c9f556bd 100644 --- a/gateway/request.py +++ b/gateway/request.py @@ -62,9 +62,13 @@ def _get_logic_module(self, service_name: str) -> LogicModule: """ Retrieve LogicModule by service name. """ if service_name not in self._logic_modules: try: - self._logic_modules[service_name] = LogicModule.objects.get(endpoint_name=service_name) + self._logic_modules[service_name] = LogicModule.objects.get( + endpoint_name=service_name + ) except LogicModule.DoesNotExist: - raise exceptions.ServiceDoesNotExist(f'Service "{service_name}" not found.') + raise exceptions.ServiceDoesNotExist( + f'Service "{service_name}" not found.' + ) return self._logic_modules[service_name] def get_datamesh(self) -> DataMesh: @@ -94,7 +98,9 @@ def perform(self) -> GatewayResponse: try: spec = self._get_swagger_spec(self.url_kwargs['service']) except exceptions.ServiceDoesNotExist as e: - return GatewayResponse(e.content, e.status, {'Content-Type': e.content_type}) + return GatewayResponse( + e.content, e.status, {'Content-Type': e.content_type} + ) # create a client for performing data requests client = SwaggerClient(spec, self.request) @@ -112,9 +118,16 @@ def perform(self) -> GatewayResponse: logger.error(e.content) path_url = self.request.path # Get request path - list_string_path = path_url.split("/") # Split the request path to check if custody include in it - if ('join' not in self.request.query_params and 'custody' in list_string_path and - status_code == 201 and type(content) in [dict, list] and self.request.method == 'POST'): + list_string_path = path_url.split( + "/" + ) # Split the request path to check if custody include in it + if ( + 'join' not in self.request.query_params + and 'custody' in list_string_path + and status_code == 201 + and type(content) in [dict, list] + and self.request.method == 'POST' + ): # This functionality will execute only when request include custody with post request, # It will not execute if its join request related_organization = content.get('organization_uuid') @@ -134,7 +147,9 @@ def perform(self) -> GatewayResponse: consortium.save() else: # If consortium does not exists for shipment name, then create consortium - Consortium.objects.create(name=shipment_name, organization_uuids=[related_organization]) + Consortium.objects.create( + name=shipment_name, organization_uuids=[related_organization] + ) if type(content) in [dict, list]: content = json.dumps(content, cls=utils.GatewayJSONEncoder) @@ -225,14 +240,18 @@ def perform(self) -> GatewayResponse: result = {} asyncio.run(self.async_perform(result)) if 'response' not in result: - raise exceptions.GatewayError('Error performing asynchronous gateway request') + raise exceptions.GatewayError( + 'Error performing asynchronous gateway request' + ) return result['response'] async def async_perform(self, result: dict): try: spec = await self._get_swagger_spec(self.url_kwargs['service']) except exceptions.ServiceDoesNotExist as e: - return GatewayResponse(e.content, e.status, {'Content-Type': e.content_type}) + return GatewayResponse( + e.content, e.status, {'Content-Type': e.content_type} + ) # create a client for performing data requests client = AsyncSwaggerClient(spec, self.request) @@ -241,7 +260,11 @@ async def async_perform(self, result: dict): content, status_code, headers = await client.request(**self.url_kwargs) # aggregate/join with the JoinRecord-models - if 'join' in self.request.query_params and status_code == 200 and type(content) in [dict, list]: + if ( + 'join' in self.request.query_params + and status_code == 200 + and type(content) in [dict, list] + ): try: await self._join_response_data(resp_data=content) except exceptions.ServiceDoesNotExist as e: diff --git a/gateway/tests/fixtures.py b/gateway/tests/fixtures.py index 87842da7..167a59bf 100644 --- a/gateway/tests/fixtures.py +++ b/gateway/tests/fixtures.py @@ -8,6 +8,33 @@ @pytest.fixture() def datamesh(): +<<<<<<< HEAD + lm1 = factories.LogicModule.create( + name='location', + endpoint_name='location', + endpoint='http://locationservice:8080', + ) + lm2 = factories.LogicModule.create( + name='documents', + endpoint_name='documents', + endpoint='http://documentservice:8080', + ) + lmm1 = factories.LogicModuleModel( + logic_module_endpoint_name=lm1.endpoint_name, + model='SiteProfile', + endpoint='/siteprofiles/', + lookup_field_name='uuid', + ) + lmm2 = factories.LogicModuleModel( + logic_module_endpoint_name=lm2.endpoint_name, + model='Document', + endpoint='/documents/', + lookup_field_name='id', + ) + relationship = factories.Relationship( + origin_model=lmm1, related_model=lmm2, key='documents' + ) +======= lm1 = factories.LogicModule.create(name='location', endpoint_name='location', endpoint='http://locationservice:8080') lm2 = factories.LogicModule.create(name='documents', endpoint_name='documents', @@ -17,14 +44,13 @@ def datamesh(): lmm2 = factories.LogicModuleModel(logic_module_endpoint_name=lm2.endpoint_name, model='Document', endpoint='/documents/', lookup_field_name='documents_id') relationship = factories.Relationship(origin_model=lmm1, related_model=lmm2, key='documents') +>>>>>>> master return lm1, lm2, relationship @pytest.fixture def aggregator(logic_module): configuration = { - 'apis': { - logic_module.endpoint_name: f'{logic_module.endpoint}/swagger.json' - } + 'apis': {logic_module.endpoint_name: f'{logic_module.endpoint}/swagger.json'} } return SwaggerAggregator(configuration) diff --git a/gateway/tests/test_permissions.py b/gateway/tests/test_permissions.py index 04db1609..604c63cb 100644 --- a/gateway/tests/test_permissions.py +++ b/gateway/tests/test_permissions.py @@ -13,53 +13,59 @@ @pytest.mark.django_db() class TestAllowLogicModuleGroup: - - def test_has_permission_superuser_success(self, auth_superuser_api_client, superuser): + def test_has_permission_superuser_success( + self, auth_superuser_api_client, superuser + ): """ Superusers are able to access all services """ - request = WSGIRequest(auth_superuser_api_client._base_environ(**auth_superuser_api_client._credentials)) + request = WSGIRequest( + auth_superuser_api_client._base_environ( + **auth_superuser_api_client._credentials + ) + ) request.user = superuser - kwargs = { - 'kwargs': {'service': 'test'}, - 'request': request - } + kwargs = {'kwargs': {'service': 'test'}, 'request': request} permission_obj = AllowLogicModuleGroup() view = APIGatewayView(**kwargs) result = permission_obj.has_permission(kwargs['request'], view) assert result - def test_has_permission_normal_user_service_doesnt_exist(self, auth_api_client, org_admin, org): - request = WSGIRequest(auth_api_client._base_environ(**auth_api_client._credentials)) + def test_has_permission_normal_user_service_doesnt_exist( + self, auth_api_client, org_admin, org + ): + request = WSGIRequest( + auth_api_client._base_environ(**auth_api_client._credentials) + ) request.user = org_admin - kwargs = { - 'kwargs': {'service': 'test'}, - 'request': request - } + kwargs = {'kwargs': {'service': 'test'}, 'request': request} permission_obj = AllowLogicModuleGroup() view = APIGatewayView(**kwargs) with pytest.raises(ServiceDoesNotExist): permission_obj.has_permission(kwargs['request'], view) - def test_has_permission_normal_user_no_restrictions(self, auth_api_client, org_admin, org, logic_module): + def test_has_permission_normal_user_no_restrictions( + self, auth_api_client, org_admin, org, logic_module + ): """ Services without permission groups can be accessed by everybody """ - request = WSGIRequest(auth_api_client._base_environ(**auth_api_client._credentials)) + request = WSGIRequest( + auth_api_client._base_environ(**auth_api_client._credentials) + ) request.user = org_admin - kwargs = { - 'kwargs': {'service': logic_module.name}, - 'request': request - } + kwargs = {'kwargs': {'service': logic_module.name}, 'request': request} permission_obj = AllowLogicModuleGroup() view = APIGatewayView(**kwargs) result = permission_obj.has_permission(kwargs['request'], view) assert result - def test_has_permission_global_level_permission(self, auth_api_client, org_admin, org, logic_module, core_group): + def test_has_permission_global_level_permission( + self, auth_api_client, org_admin, org, logic_module, core_group + ): """ Global level permissions of a service are applied to all users who try to access it """ @@ -69,39 +75,40 @@ def test_has_permission_global_level_permission(self, auth_api_client, org_admin core_group.save() logic_module.core_groups.add(core_group) - request = WSGIRequest(auth_api_client._base_environ(**auth_api_client._credentials)) + request = WSGIRequest( + auth_api_client._base_environ(**auth_api_client._credentials) + ) request.user = org_admin - kwargs = { - 'kwargs': {'service': logic_module.name}, - 'request': request - } + kwargs = {'kwargs': {'service': logic_module.name}, 'request': request} permission_obj = AllowLogicModuleGroup() view = APIGatewayView(**kwargs) result = permission_obj.has_permission(kwargs['request'], view) assert result - def test_has_permission_normal_user_org_level_permission(self, auth_api_client, org_admin, org, logic_module): + def test_has_permission_normal_user_org_level_permission( + self, auth_api_client, org_admin, org, logic_module + ): """ Organization level permissions of a service are applied to users of a specific org """ org_admin_group = org_admin.core_groups.all().first() logic_module.core_groups.add(org_admin_group) - request = WSGIRequest(auth_api_client._base_environ(**auth_api_client._credentials)) + request = WSGIRequest( + auth_api_client._base_environ(**auth_api_client._credentials) + ) request.user = org_admin - kwargs = { - 'kwargs': {'service': logic_module.name}, - 'request': request - } + kwargs = {'kwargs': {'service': logic_module.name}, 'request': request} permission_obj = AllowLogicModuleGroup() view = APIGatewayView(**kwargs) result = permission_obj.has_permission(kwargs['request'], view) assert result - def test_has_permission_normal_user_org_level_diff_org(self, auth_api_client, org_admin, org, logic_module, - core_group): + def test_has_permission_normal_user_org_level_diff_org( + self, auth_api_client, org_admin, org, logic_module, core_group + ): """ Organization level permissions of a service aren't applied to users from an org different to the one from the permissions @@ -112,12 +119,11 @@ def test_has_permission_normal_user_org_level_diff_org(self, auth_api_client, or core_group.save() logic_module.core_groups.add(core_group) - request = WSGIRequest(auth_api_client._base_environ(**auth_api_client._credentials)) + request = WSGIRequest( + auth_api_client._base_environ(**auth_api_client._credentials) + ) request.user = org_admin - kwargs = { - 'kwargs': {'service': logic_module.name}, - 'request': request - } + kwargs = {'kwargs': {'service': logic_module.name}, 'request': request} permission_obj = AllowLogicModuleGroup() view = APIGatewayView(**kwargs) diff --git a/gateway/tests/test_swagger_aggregator.py b/gateway/tests/test_swagger_aggregator.py index fa599ec1..b4910a46 100644 --- a/gateway/tests/test_swagger_aggregator.py +++ b/gateway/tests/test_swagger_aggregator.py @@ -7,8 +7,9 @@ @pytest.mark.django_db() class TestSwaggerAggregator: - - def test_get_aggregate_swagger_without_api_specification(self, aggregator, logic_module, monkeypatch): + def test_get_aggregate_swagger_without_api_specification( + self, aggregator, logic_module, monkeypatch + ): test_swagger = {'name': 'test'} mocked_swagger = Mock() mocked_swagger.return_value.json.return_value = test_swagger @@ -22,7 +23,9 @@ def test_get_aggregate_swagger_without_api_specification(self, aggregator, logic mocked_swagger.assert_called_once() - def test_get_aggregate_swagger_with_api_specification(self, aggregator, logic_module, monkeypatch): + def test_get_aggregate_swagger_with_api_specification( + self, aggregator, logic_module, monkeypatch + ): test_swagger = {'name': 'test'} logic_module.api_specification = test_swagger logic_module.save() @@ -38,7 +41,9 @@ def test_get_aggregate_swagger_with_api_specification(self, aggregator, logic_mo mocked_swagger.assert_not_called() - def test_get_aggregate_swagger_connection_error(self, aggregator, logic_module, monkeypatch): + def test_get_aggregate_swagger_connection_error( + self, aggregator, logic_module, monkeypatch + ): mocked_swagger = Mock(side_effect=ConnectionError) monkeypatch.setattr(utils, 'get_swagger_from_url', mocked_swagger) @@ -46,7 +51,9 @@ def test_get_aggregate_swagger_connection_error(self, aggregator, logic_module, assert result == {} mocked_swagger.assert_called_once() - def test_get_aggregate_swagger_timeout_error(self, aggregator, logic_module, monkeypatch): + def test_get_aggregate_swagger_timeout_error( + self, aggregator, logic_module, monkeypatch + ): mocked_swagger = Mock(side_effect=TimeoutError) monkeypatch.setattr(utils, 'get_swagger_from_url', mocked_swagger) @@ -54,7 +61,9 @@ def test_get_aggregate_swagger_timeout_error(self, aggregator, logic_module, mon assert result == {} mocked_swagger.assert_called_once() - def test_get_aggregate_swagger_value_error(self, aggregator, logic_module, monkeypatch): + def test_get_aggregate_swagger_value_error( + self, aggregator, logic_module, monkeypatch + ): mocked_swagger = Mock(side_effect=ValueError) monkeypatch.setattr(utils, 'get_swagger_from_url', mocked_swagger) diff --git a/gateway/tests/test_urls.py b/gateway/tests/test_urls.py index d61b7d15..13dc788e 100644 --- a/gateway/tests/test_urls.py +++ b/gateway/tests/test_urls.py @@ -4,24 +4,34 @@ class URLPatternsTest(TestCase): def test_api_gateway_urls_with_fragment(self): - for url in ('/crm/appointment/#some-section', - '/crm/appointment#some-section'): + for url in ('/crm/appointment/#some-section', '/crm/appointment#some-section'): match = resolve(url) self.assertEqual(match.url_name, 'api-gateway') self.assertDictContainsSubset( - {'service': 'crm', 'model': 'appointment', 'pk': None, - 'fragment': 'some-section'}, - match.kwargs, f'Failing URL: {url}') + { + 'service': 'crm', + 'model': 'appointment', + 'pk': None, + 'fragment': 'some-section', + }, + match.kwargs, + f'Failing URL: {url}', + ) def test_api_gateway_urls_with_queryparams(self): - for url in ('/crm/appointment/?k1=v1&k2=v2', - '/crm/appointment?k1=v1&k2=v2'): + for url in ('/crm/appointment/?k1=v1&k2=v2', '/crm/appointment?k1=v1&k2=v2'): match = resolve(url) self.assertEqual(match.url_name, 'api-gateway') self.assertDictContainsSubset( - {'service': 'crm', 'model': 'appointment', 'pk': None, - 'query': 'k1=v1&k2=v2'}, - match.kwargs, f'Failing URL: {url}') + { + 'service': 'crm', + 'model': 'appointment', + 'pk': None, + 'query': 'k1=v1&k2=v2', + }, + match.kwargs, + f'Failing URL: {url}', + ) def test_api_gateway_urls_with_int_pk(self): for url in ('/crm/appointment/123456/', '/crm/appointment/123456'): @@ -29,29 +39,34 @@ def test_api_gateway_urls_with_int_pk(self): self.assertEqual(match.url_name, 'api-gateway') self.assertDictContainsSubset( {'service': 'crm', 'model': 'appointment', 'pk': '123456'}, - match.kwargs, f'Failing URL: {url}') + match.kwargs, + f'Failing URL: {url}', + ) def test_api_gateway_urls_with_uuid_pk(self): - match = resolve( - '/crm/appointment/39da9369-838e-4750-91a5-f7805cd82839/') + match = resolve('/crm/appointment/39da9369-838e-4750-91a5-f7805cd82839/') self.assertEqual(match.url_name, 'api-gateway') self.assertDictContainsSubset( - {'service': 'crm', 'model': 'appointment', - 'pk': '39da9369-838e-4750-91a5-f7805cd82839'}, - match.kwargs) + { + 'service': 'crm', + 'model': 'appointment', + 'pk': '39da9369-838e-4750-91a5-f7805cd82839', + }, + match.kwargs, + ) def test_api_gateway_urls_without_pk(self): match = resolve('/crm/appointment/') self.assertEqual(match.url_name, 'api-gateway') self.assertDictContainsSubset( - {'service': 'crm', 'model': 'appointment', 'pk': None}, - match.kwargs) + {'service': 'crm', 'model': 'appointment', 'pk': None}, match.kwargs + ) match = resolve('/crm/appointment') self.assertEqual(match.url_name, 'api-gateway') self.assertDictContainsSubset( - {'service': 'crm', 'model': 'appointment', 'pk': None}, - match.kwargs) + {'service': 'crm', 'model': 'appointment', 'pk': None}, match.kwargs + ) def test_admin_url(self): match = resolve('/admin/') diff --git a/gateway/tests/test_utils.py b/gateway/tests/test_utils.py index b619f7d8..b7d084a5 100644 --- a/gateway/tests/test_utils.py +++ b/gateway/tests/test_utils.py @@ -14,7 +14,13 @@ import factories from gateway.exceptions import GatewayError -from gateway.utils import GatewayJSONEncoder, validate_object_access, get_swagger_url_by_logic_module, get_swagger_urls, get_swagger_from_url +from gateway.utils import ( + GatewayJSONEncoder, + validate_object_access, + get_swagger_url_by_logic_module, + get_swagger_urls, + get_swagger_from_url, +) from gateway.views import APIGatewayView @@ -37,8 +43,7 @@ def test_validate_buildly_wfl1_access_superuser(self): self.core_user.save() request = self.get_mock_request('/', APIGatewayView, self.core_user) - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) validate_object_access(request, wflvl1) def test_validate_buildly_wfl1_no_permission(self): @@ -65,7 +70,9 @@ def test_validate_buildly_logic_module_no_viewset(self): validate_object_access(request, lm) def test_validate_core_user_access(self): - request = self.get_mock_request('/a-jedis-path/', APIGatewayView, self.core_user) + request = self.get_mock_request( + '/a-jedis-path/', APIGatewayView, self.core_user + ) request.resolver_match = Mock(url_name='obi-wan-kenobi') core_user = factories.CoreUser() ret = validate_object_access(request, core_user) @@ -76,33 +83,32 @@ def test_json_dump(): obj = { "string": "test1234", "integer": 123, - "array": ['1', 2, ], + "array": ['1', 2], "uuid": uuid.UUID('50096bc6-848a-456f-ad36-3ac04607ff67'), "datetime": datetime.datetime(2019, 2, 5, 12, 36, 0, 147972), } response = json.dumps(obj, cls=GatewayJSONEncoder) - expected_response = '{"string": "test1234",' \ - ' "integer": 123,' \ - ' "array": ["1", 2],' \ - ' "uuid": "50096bc6-848a-456f-ad36-3ac04607ff67",' \ - ' "datetime": "2019-02-05T12:36:00.147972"}' + expected_response = ( + '{"string": "test1234",' + ' "integer": 123,' + ' "array": ["1", 2],' + ' "uuid": "50096bc6-848a-456f-ad36-3ac04607ff67",' + ' "datetime": "2019-02-05T12:36:00.147972"}' + ) assert response == expected_response @pytest.mark.django_db() def test_json_dump_w_core_user(): core_user = factories.CoreUser(pk=5) - obj = { - "model_instance": core_user, - } + obj = {"model_instance": core_user} result = json.dumps(obj, cls=GatewayJSONEncoder) expected_result = '{"model_instance": 5}' assert result == expected_result def test_json_dump_exception(): - class TestObj(object): pass @@ -117,12 +123,11 @@ class TestObj(object): @pytest.mark.django_db() class TestGettingSwaggerURLs: - def test_get_swagger_url_by_logic_module(self): module = factories.LogicModule.create() url = get_swagger_url_by_logic_module(module) assert url.startswith(module.endpoint) - + def test_get_swagger_url_by_logic_module_specified_docs(self): module = factories.LogicModule.create(docs_endpoint="api-docs") url = get_swagger_url_by_logic_module(module) @@ -135,7 +140,7 @@ def test_get_swagger_urls(self): for module in modules: assert module.endpoint_name in urls assert urls[module.endpoint_name] == get_swagger_url_by_logic_module(module) - + @patch('requests.get') def test_unavailable_logic_module_timeout_exception(self, mock_request_get): mock_request_get.side_effect = TimeoutError diff --git a/gateway/tests/test_views.py b/gateway/tests/test_views.py index ff55ebe8..532a3931 100755 --- a/gateway/tests/test_views.py +++ b/gateway/tests/test_views.py @@ -12,11 +12,18 @@ CURRENT_PATH = os.path.dirname(os.path.abspath(__file__)) -@pytest.mark.parametrize("content,content_type", [('{"details": "IT IS A TEST"}', 'application/json'), - ('IT IS A TEST', 'text/html; charset=utf-8')]) +@pytest.mark.parametrize( + "content,content_type", + [ + ('{"details": "IT IS A TEST"}', 'application/json'), + ('IT IS A TEST', 'text/html; charset=utf-8'), + ], +) @pytest.mark.django_db() @httpretty.activate -def test_make_service_request_data_and_raw(auth_api_client, logic_module, content, content_type): +def test_make_service_request_data_and_raw( + auth_api_client, logic_module, content, content_type +): url = f'/{logic_module.endpoint_name}/thumbnail/1/' # mock requests @@ -26,13 +33,13 @@ def test_make_service_request_data_and_raw(auth_api_client, logic_module, conten httpretty.GET, f'{logic_module.endpoint}/docs/swagger.json', body=swagger_body, - adding_headers={'Content-Type': 'application/json'} + adding_headers={'Content-Type': 'application/json'}, ) httpretty.register_uri( httpretty.GET, f'{logic_module.endpoint}/thumbnail/1/', body=content, - adding_headers={'Content-Type': content_type} + adding_headers={'Content-Type': content_type}, ) # make api request @@ -46,7 +53,9 @@ def test_make_service_request_data_and_raw(auth_api_client, logic_module, conten @pytest.mark.django_db() @httpretty.activate -def test_make_service_request_to_unexisting_list_endpoint(auth_api_client, logic_module): +def test_make_service_request_to_unexisting_list_endpoint( + auth_api_client, logic_module +): url = f'/{logic_module.endpoint_name}/nowhere/' @@ -57,7 +66,7 @@ def test_make_service_request_to_unexisting_list_endpoint(auth_api_client, logic httpretty.GET, f'{logic_module.endpoint}/docs/swagger.json', body=swagger_body, - adding_headers={'Content-Type': 'application/json'} + adding_headers={'Content-Type': 'application/json'}, ) # make api request @@ -72,7 +81,9 @@ def test_make_service_request_to_unexisting_list_endpoint(auth_api_client, logic @pytest.mark.django_db() @httpretty.activate -def test_make_service_request_to_unexisting_detail_endpoint(auth_api_client, logic_module): +def test_make_service_request_to_unexisting_detail_endpoint( + auth_api_client, logic_module +): url = f'/{logic_module.endpoint_name}/nowhere/123/' @@ -83,7 +94,7 @@ def test_make_service_request_to_unexisting_detail_endpoint(auth_api_client, log httpretty.GET, f'{logic_module.endpoint}/docs/swagger.json', body=swagger_body, - adding_headers={'Content-Type': 'application/json'} + adding_headers={'Content-Type': 'application/json'}, ) # make api request @@ -100,9 +111,13 @@ def test_make_service_request_to_unexisting_detail_endpoint(auth_api_client, log @httpretty.activate def test_make_service_request_with_datamesh_detailed(auth_api_client, datamesh): lm1, lm2, relationship = datamesh - factories.JoinRecord(relationship=relationship, - record_id=None, record_uuid='19a7f600-74a0-4123-9be5-dfa69aa172cc', - related_record_id=1, related_record_uuid=None) + factories.JoinRecord( + relationship=relationship, + record_id=None, + record_uuid='19a7f600-74a0-4123-9be5-dfa69aa172cc', + related_record_id=1, + related_record_uuid=None, + ) url = f'/{lm1.endpoint_name}/siteprofiles/19a7f600-74a0-4123-9be5-dfa69aa172cc/' @@ -119,25 +134,25 @@ def test_make_service_request_with_datamesh_detailed(auth_api_client, datamesh): httpretty.GET, f'{lm1.endpoint}/docs/swagger.json', body=swagger_location_body, - adding_headers={'Content-Type': 'application/json'} + adding_headers={'Content-Type': 'application/json'}, ) httpretty.register_uri( httpretty.GET, f'{lm2.endpoint}/docs/swagger.json', body=swagger_documents_body, - adding_headers={'Content-Type': 'application/json'} + adding_headers={'Content-Type': 'application/json'}, ) httpretty.register_uri( httpretty.GET, f'{lm1.endpoint}/siteprofiles/19a7f600-74a0-4123-9be5-dfa69aa172cc/', body=data_location_body, - adding_headers={'Content-Type': 'application/json'} + adding_headers={'Content-Type': 'application/json'}, ) httpretty.register_uri( httpretty.GET, f'{lm2.endpoint}/documents/1/', body=data_documents_body, - adding_headers={'Content-Type': 'application/json'} + adding_headers={'Content-Type': 'application/json'}, ) # make api request @@ -176,25 +191,25 @@ def test_make_service_request_with_reverse_datamesh_detailed(auth_api_client, da httpretty.GET, f'{lm1.endpoint}/docs/swagger.json', body=swagger_location_body, - adding_headers={'Content-Type': 'application/json'} + adding_headers={'Content-Type': 'application/json'}, ) httpretty.register_uri( httpretty.GET, f'{lm2.endpoint}/docs/swagger.json', body=swagger_documents_body, - adding_headers={'Content-Type': 'application/json'} + adding_headers={'Content-Type': 'application/json'}, ) httpretty.register_uri( httpretty.GET, f'{lm1.endpoint}/siteprofiles/19a7f600-74a0-4123-9be5-dfa69aa172cc/', body=data_location_body, - adding_headers={'Content-Type': 'application/json'} + adding_headers={'Content-Type': 'application/json'}, ) httpretty.register_uri( httpretty.GET, f'{lm2.endpoint}/documents/1/', body=data_documents_body, - adding_headers={'Content-Type': 'application/json'} + adding_headers={'Content-Type': 'application/json'}, ) # make api request @@ -213,9 +228,13 @@ def test_make_service_request_with_reverse_datamesh_detailed(auth_api_client, da @httpretty.activate def test_make_service_request_with_datamesh_list(auth_api_client, datamesh): lm1, lm2, relationship = datamesh - factories.JoinRecord(relationship=relationship, - record_id=None, record_uuid='19a7f600-74a0-4123-9be5-dfa69aa172cc', - related_record_id=1, related_record_uuid=None) + factories.JoinRecord( + relationship=relationship, + record_id=None, + record_uuid='19a7f600-74a0-4123-9be5-dfa69aa172cc', + related_record_id=1, + related_record_uuid=None, + ) url = f'/{lm1.endpoint_name}/siteprofiles/' @@ -232,25 +251,25 @@ def test_make_service_request_with_datamesh_list(auth_api_client, datamesh): httpretty.GET, f'{lm1.endpoint}/docs/swagger.json', body=swagger_location_body, - adding_headers={'Content-Type': 'application/json'} + adding_headers={'Content-Type': 'application/json'}, ) httpretty.register_uri( httpretty.GET, f'{lm2.endpoint}/docs/swagger.json', body=swagger_documents_body, - adding_headers={'Content-Type': 'application/json'} + adding_headers={'Content-Type': 'application/json'}, ) httpretty.register_uri( httpretty.GET, f'{lm1.endpoint}/siteprofiles/', body=data_location_body, - adding_headers={'Content-Type': 'application/json'} + adding_headers={'Content-Type': 'application/json'}, ) httpretty.register_uri( httpretty.GET, f'{lm2.endpoint}/documents/1/', body=data_documents_body, - adding_headers={'Content-Type': 'application/json'} + adding_headers={'Content-Type': 'application/json'}, ) # make api request diff --git a/gateway/tests/test_views_async.py b/gateway/tests/test_views_async.py index fbd95191..484e0cea 100644 --- a/gateway/tests/test_views_async.py +++ b/gateway/tests/test_views_async.py @@ -13,15 +13,24 @@ CURRENT_PATH = os.path.dirname(os.path.abspath(__file__)) -@pytest.mark.parametrize("content,content_type", [ - (b'{"details": "IT IS A TEST"}', 'application/json'), - (b'IT IS A TEST', 'text/html; charset=utf-8'), - (None, 'application/octet-stream'), ] +@pytest.mark.parametrize( + "content,content_type", + [ + (b'{"details": "IT IS A TEST"}', 'application/json'), + (b'IT IS A TEST', 'text/html; charset=utf-8'), + (None, 'application/octet-stream'), + ], ) @pytest.mark.django_db() @patch('gateway.request.aiohttp.ClientSession') -def test_make_service_request_data_and_raw(client_session_mock, auth_api_client, logic_module, content, content_type, - event_loop): +def test_make_service_request_data_and_raw( + client_session_mock, + auth_api_client, + logic_module, + content, + content_type, + event_loop, +): url = f'/async/{logic_module.endpoint_name}/thumbnail/1/' # mock aiohttp responses @@ -29,12 +38,24 @@ def test_make_service_request_data_and_raw(client_session_mock, auth_api_client, swagger_body = r.read() responses = [ - AiohttpResponseMock(method='GET', url=f'{logic_module.endpoint}/docs/swagger.json', status=200, - body=swagger_body, headers={'Content-Type': 'application/json'}), - AiohttpResponseMock(method='GET', url=f'{logic_module.endpoint}/thumbnail/1/', status=200, body=content, - headers={'Content-Type': content_type}), + AiohttpResponseMock( + method='GET', + url=f'{logic_module.endpoint}/docs/swagger.json', + status=200, + body=swagger_body, + headers={'Content-Type': 'application/json'}, + ), + AiohttpResponseMock( + method='GET', + url=f'{logic_module.endpoint}/thumbnail/1/', + status=200, + body=content, + headers={'Content-Type': content_type}, + ), ] - client_session_mock.return_value = create_aiohttp_session_mock(responses, loop=event_loop) + client_session_mock.return_value = create_aiohttp_session_mock( + responses, loop=event_loop + ) # make api request response = auth_api_client.get(url) @@ -50,8 +71,9 @@ def test_make_service_request_data_and_raw(client_session_mock, auth_api_client, @pytest.mark.django_db() @patch('gateway.request.aiohttp.ClientSession') -def test_make_service_request_to_unexisting_list_endpoint(client_session_mock, auth_api_client, logic_module, - event_loop): +def test_make_service_request_to_unexisting_list_endpoint( + client_session_mock, auth_api_client, logic_module, event_loop +): url = f'/async/{logic_module.endpoint_name}/nowhere/' @@ -59,10 +81,17 @@ def test_make_service_request_to_unexisting_list_endpoint(client_session_mock, a with open(os.path.join(CURRENT_PATH, 'fixtures/swagger_documents.json'), 'rb') as r: swagger_body = r.read() responses = [ - AiohttpResponseMock(method='GET', url=f'{logic_module.endpoint}/docs/swagger.json', status=200, - body=swagger_body, headers={'Content-Type': 'application/json'}), + AiohttpResponseMock( + method='GET', + url=f'{logic_module.endpoint}/docs/swagger.json', + status=200, + body=swagger_body, + headers={'Content-Type': 'application/json'}, + ) ] - client_session_mock.return_value = create_aiohttp_session_mock(responses, loop=event_loop) + client_session_mock.return_value = create_aiohttp_session_mock( + responses, loop=event_loop + ) # make api request response = auth_api_client.get(url) @@ -76,8 +105,9 @@ def test_make_service_request_to_unexisting_list_endpoint(client_session_mock, a @pytest.mark.django_db() @patch('gateway.request.aiohttp.ClientSession') -def test_make_service_request_to_unexisting_detail_endpoint(client_session_mock, auth_api_client, logic_module, - event_loop): +def test_make_service_request_to_unexisting_detail_endpoint( + client_session_mock, auth_api_client, logic_module, event_loop +): url = f'/async/{logic_module.endpoint_name}/nowhere/123/' @@ -85,10 +115,17 @@ def test_make_service_request_to_unexisting_detail_endpoint(client_session_mock, with open(os.path.join(CURRENT_PATH, 'fixtures/swagger_documents.json'), 'rb') as r: swagger_body = r.read() responses = [ - AiohttpResponseMock(method='GET', url=f'{logic_module.endpoint}/docs/swagger.json', status=200, - body=swagger_body, headers={'Content-Type': 'application/json'}), + AiohttpResponseMock( + method='GET', + url=f'{logic_module.endpoint}/docs/swagger.json', + status=200, + body=swagger_body, + headers={'Content-Type': 'application/json'}, + ) ] - client_session_mock.return_value = create_aiohttp_session_mock(responses, loop=event_loop) + client_session_mock.return_value = create_aiohttp_session_mock( + responses, loop=event_loop + ) # make api request response = auth_api_client.get(url) @@ -102,35 +139,68 @@ def test_make_service_request_to_unexisting_detail_endpoint(client_session_mock, @pytest.mark.django_db() @patch('gateway.request.aiohttp.ClientSession') -def test_make_service_request_with_datamesh_detailed(client_session_mock, auth_api_client, datamesh, event_loop): +def test_make_service_request_with_datamesh_detailed( + client_session_mock, auth_api_client, datamesh, event_loop +): lm1, lm2, relationship = datamesh - factories.JoinRecord(relationship=relationship, - record_id=None, record_uuid='19a7f600-74a0-4123-9be5-dfa69aa172cc', - related_record_id=1, related_record_uuid=None) - - url = f'/async/{lm1.endpoint_name}/siteprofiles/19a7f600-74a0-4123-9be5-dfa69aa172cc/' + factories.JoinRecord( + relationship=relationship, + record_id=None, + record_uuid='19a7f600-74a0-4123-9be5-dfa69aa172cc', + related_record_id=1, + related_record_uuid=None, + ) + + url = ( + f'/async/{lm1.endpoint_name}/siteprofiles/19a7f600-74a0-4123-9be5-dfa69aa172cc/' + ) # mock aiohttp responses with open(os.path.join(CURRENT_PATH, 'fixtures/swagger_location.json'), 'rb') as r: swagger_location_body = r.read() with open(os.path.join(CURRENT_PATH, 'fixtures/swagger_documents.json'), 'rb') as r: swagger_documents_body = r.read() - with open(os.path.join(CURRENT_PATH, 'fixtures/data_detail_siteprofile.json'), 'rb') as r: + with open( + os.path.join(CURRENT_PATH, 'fixtures/data_detail_siteprofile.json'), 'rb' + ) as r: data_location_body = r.read() - with open(os.path.join(CURRENT_PATH, 'fixtures/data_detail_document.json'), 'rb') as r: + with open( + os.path.join(CURRENT_PATH, 'fixtures/data_detail_document.json'), 'rb' + ) as r: data_documents_body = r.read() responses = [ - AiohttpResponseMock(method='GET', url=f'{lm1.endpoint}/docs/swagger.json', status=200, - body=swagger_location_body, headers={'Content-Type': 'application/json'}), - AiohttpResponseMock(method='GET', url=f'{lm2.endpoint}/docs/swagger.json', status=200, - body=swagger_documents_body, headers={'Content-Type': 'application/json'}), - AiohttpResponseMock(method='GET', url=f'{lm1.endpoint}/siteprofiles/19a7f600-74a0-4123-9be5-dfa69aa172cc/', - status=200, - body=data_location_body, headers={'Content-Type': 'application/json'}), - AiohttpResponseMock(method='GET', url=f'{lm2.endpoint}/documents/1/', status=200, - body=data_documents_body, headers={'Content-Type': 'application/json'}), + AiohttpResponseMock( + method='GET', + url=f'{lm1.endpoint}/docs/swagger.json', + status=200, + body=swagger_location_body, + headers={'Content-Type': 'application/json'}, + ), + AiohttpResponseMock( + method='GET', + url=f'{lm2.endpoint}/docs/swagger.json', + status=200, + body=swagger_documents_body, + headers={'Content-Type': 'application/json'}, + ), + AiohttpResponseMock( + method='GET', + url=f'{lm1.endpoint}/siteprofiles/19a7f600-74a0-4123-9be5-dfa69aa172cc/', + status=200, + body=data_location_body, + headers={'Content-Type': 'application/json'}, + ), + AiohttpResponseMock( + method='GET', + url=f'{lm2.endpoint}/documents/1/', + status=200, + body=data_documents_body, + headers={'Content-Type': 'application/json'}, + ), ] - client_session_mock.return_value = create_aiohttp_session_mock(responses, loop=event_loop) + client_session_mock.return_value = create_aiohttp_session_mock( + responses, loop=event_loop + ) # make api request response = auth_api_client.get(url, {'join': 'true'}) @@ -146,11 +216,17 @@ def test_make_service_request_with_datamesh_detailed(client_session_mock, auth_a @pytest.mark.django_db() @patch('gateway.request.aiohttp.ClientSession') -def test_make_service_request_with_datamesh_list(client_session_mock, auth_api_client, datamesh, event_loop): +def test_make_service_request_with_datamesh_list( + client_session_mock, auth_api_client, datamesh, event_loop +): lm1, lm2, relationship = datamesh - factories.JoinRecord(relationship=relationship, - record_id=None, record_uuid='19a7f600-74a0-4123-9be5-dfa69aa172cc', - related_record_id=1, related_record_uuid=None) + factories.JoinRecord( + relationship=relationship, + record_id=None, + record_uuid='19a7f600-74a0-4123-9be5-dfa69aa172cc', + related_record_id=1, + related_record_uuid=None, + ) url = f'/async/{lm1.endpoint_name}/siteprofiles/' @@ -159,21 +235,47 @@ def test_make_service_request_with_datamesh_list(client_session_mock, auth_api_c swagger_location_body = r.read() with open(os.path.join(CURRENT_PATH, 'fixtures/swagger_documents.json'), 'rb') as r: swagger_documents_body = r.read() - with open(os.path.join(CURRENT_PATH, 'fixtures/data_list_siteprofile.json'), 'rb') as r: + with open( + os.path.join(CURRENT_PATH, 'fixtures/data_list_siteprofile.json'), 'rb' + ) as r: data_location_body = r.read() - with open(os.path.join(CURRENT_PATH, 'fixtures/data_detail_document.json'), 'rb') as r: + with open( + os.path.join(CURRENT_PATH, 'fixtures/data_detail_document.json'), 'rb' + ) as r: data_documents_body = r.read() responses = [ - AiohttpResponseMock(method='GET', url=f'{lm1.endpoint}/docs/swagger.json', status=200, - body=swagger_location_body, headers={'Content-Type': 'application/json'}), - AiohttpResponseMock(method='GET', url=f'{lm2.endpoint}/docs/swagger.json', status=200, - body=swagger_documents_body, headers={'Content-Type': 'application/json'}), - AiohttpResponseMock(method='GET', url=f'{lm1.endpoint}/siteprofiles/', status=200, - body=data_location_body, headers={'Content-Type': 'application/json'}), - AiohttpResponseMock(method='GET', url=f'{lm2.endpoint}/documents/1/', status=200, - body=data_documents_body, headers={'Content-Type': 'application/json'}), + AiohttpResponseMock( + method='GET', + url=f'{lm1.endpoint}/docs/swagger.json', + status=200, + body=swagger_location_body, + headers={'Content-Type': 'application/json'}, + ), + AiohttpResponseMock( + method='GET', + url=f'{lm2.endpoint}/docs/swagger.json', + status=200, + body=swagger_documents_body, + headers={'Content-Type': 'application/json'}, + ), + AiohttpResponseMock( + method='GET', + url=f'{lm1.endpoint}/siteprofiles/', + status=200, + body=data_location_body, + headers={'Content-Type': 'application/json'}, + ), + AiohttpResponseMock( + method='GET', + url=f'{lm2.endpoint}/documents/1/', + status=200, + body=data_documents_body, + headers={'Content-Type': 'application/json'}, + ), ] - client_session_mock.return_value = create_aiohttp_session_mock(responses, loop=event_loop) + client_session_mock.return_value = create_aiohttp_session_mock( + responses, loop=event_loop + ) # make api request response = auth_api_client.get(url, {'join': 'true'}) diff --git a/gateway/tests/utils.py b/gateway/tests/utils.py index e6fbbe27..3355acb8 100644 --- a/gateway/tests/utils.py +++ b/gateway/tests/utils.py @@ -7,7 +7,6 @@ class AiohttpResponseMock: - def __init__(self, method, url, status, body, headers=None): self.method = method self.url = url @@ -44,7 +43,10 @@ def text(self, encoding='utf-8'): @asyncio.coroutine def json(self, encoding='utf-8'): if not getattr(self.body, "decode", False): - raise ContentTypeError(request_info=RequestInfo(self.url, self.method, self.headers), history=[self]) + raise ContentTypeError( + request_info=RequestInfo(self.url, self.method, self.headers), + history=[self], + ) return json.loads(self.body.decode(encoding)) @asyncio.coroutine @@ -52,9 +54,10 @@ def release(self): pass -def create_aiohttp_session_mock(response_mocks: typing.Iterable[AiohttpResponseMock], - loop: asyncio.AbstractEventLoop = None) -> ClientSession: - +def create_aiohttp_session_mock( + response_mocks: typing.Iterable[AiohttpResponseMock], + loop: asyncio.AbstractEventLoop = None, +) -> ClientSession: async def _request(method, url, *args, **kwargs): for response in response_mocks: if response.match_request(method, url): diff --git a/gateway/urls.py b/gateway/urls.py index ef8d7e6f..6918d472 100644 --- a/gateway/urls.py +++ b/gateway/urls.py @@ -18,7 +18,7 @@ swagger_info, public=True, permission_classes=(permissions.AllowAny,), - generator_class=generator.OpenAPISchemaGenerator + generator_class=generator.OpenAPISchemaGenerator, ) urlpatterns = [ @@ -30,7 +30,9 @@ r"(?:(?P[^?#/]+)/?)?" # pk (numeric or UUID) r"(?:\?(?P[^#]*))?" # queryparams (?key1=value1&key2=value2) r"(?:#(?P.*))?", # fragment (#some-anchor) - views.APIAsyncGatewayView.as_view(), name='api-gateway-async'), + views.APIAsyncGatewayView.as_view(), + name='api-gateway-async', + ), re_path( rf"^(?!{'|'.join(API_GATEWAY_RESERVED_NAMES)})" # Reject any of these r"(?P[^/?#]+)/" # service (timetracking) @@ -38,10 +40,17 @@ r"(?:(?P[^?#/]+)/?)?" # pk (numeric or UUID) r"(?:\?(?P[^#]*))?" # queryparams (?key1=value1&key2=value2) r"(?:#(?P.*))?", # fragment (#some-anchor) - views.APIGatewayView.as_view(), name='api-gateway'), - re_path(r'^docs/swagger(?P\.json|\.yaml)$', - schema_view.without_ui(cache_timeout=0), - name='schema-swagger-json'), - path('docs/', schema_view.with_ui('swagger', cache_timeout=0), - name='schema-swagger-ui'), + views.APIGatewayView.as_view(), + name='api-gateway', + ), + re_path( + r'^docs/swagger(?P\.json|\.yaml)$', + schema_view.without_ui(cache_timeout=0), + name='schema-swagger-json', + ), + path( + 'docs/', + schema_view.with_ui('swagger', cache_timeout=0), + name='schema-swagger-ui', + ), ] diff --git a/gateway/utils.py b/gateway/utils.py index 9d561e72..5880a7bf 100644 --- a/gateway/utils.py +++ b/gateway/utils.py @@ -39,8 +39,12 @@ def get_swagger_url_by_logic_module(module: LogicModule) -> str: :param LogicModule module: the logic module (service) :return: OpenAPI schema URL for the logic module """ - swagger_lookup = module.docs_endpoint if module.docs_endpoint else SWAGGER_LOOKUP_PATH - return '{}/{}/{}.{}'.format(module.endpoint, swagger_lookup, SWAGGER_LOOKUP_FIELD, SWAGGER_LOOKUP_FORMAT) + swagger_lookup = ( + module.docs_endpoint if module.docs_endpoint else SWAGGER_LOOKUP_PATH + ) + return '{}/{}/{}.{}'.format( + module.endpoint, swagger_lookup, SWAGGER_LOOKUP_FIELD, SWAGGER_LOOKUP_FORMAT + ) def get_swagger_urls() -> Dict[str, str]: @@ -71,10 +75,10 @@ def get_swagger_from_url(api_url: str): return requests.get(api_url) except requests.exceptions.ConnectTimeout as error: raise TimeoutError( - f'Connection timed out. Please, check that {api_url} is accessible.') from error + f'Connection timed out. Please, check that {api_url} is accessible.' + ) from error except requests.exceptions.ConnectionError as error: - raise ConnectionError( - f'Please, check that {api_url} is accessible.') from error + raise ConnectionError(f'Please, check that {api_url} is accessible.') from error def validate_object_access(request: Request, obj): @@ -92,7 +96,8 @@ def validate_object_access(request: Request, obj): except KeyError: logging.critical(f'{model} needs to be added to MODEL_VIEWSETS_DICT') raise exceptions.GatewayError( - msg=f'{model} not defined for object access lookup.') + msg=f'{model} not defined for object access lookup.' + ) else: viewset.request = request viewset.check_object_permissions(request, obj) @@ -134,7 +139,9 @@ def default(self, obj): def valid_uuid4(uuid_string): - uuid4hex = re.compile('^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}\Z', # noqa - re.I) + uuid4hex = re.compile( + '^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}\Z', # noqa + re.I, + ) match = uuid4hex.match(uuid_string) return bool(match) diff --git a/gateway/views.py b/gateway/views.py index 01a25473..cf0b686a 100644 --- a/gateway/views.py +++ b/gateway/views.py @@ -56,20 +56,27 @@ def make_service_request(self, request, *args, **kwargs): try: self._validate_incoming_request(request, **kwargs) except exceptions.RequestValidationError as e: - return HttpResponse(content=e.content, status=e.status, content_type=e.content_type) + return HttpResponse( + content=e.content, status=e.status, content_type=e.content_type + ) gw_request = self.gateway_request_class(request, **kwargs) gw_response = gw_request.perform() - return HttpResponse(content=gw_response.content, - status=gw_response.status_code, - content_type=gw_response.headers.get('Content-Type')) + return HttpResponse( + content=gw_response.content, + status=gw_response.status_code, + content_type=gw_response.headers.get('Content-Type'), + ) def _validate_incoming_request(self, request: Request, **kwargs: dict) -> None: """ Do certain validations to the request before starting to create a new request to services """ - if request.META['REQUEST_METHOD'] in ['PUT', 'PATCH', 'DELETE'] and kwargs['pk'] is None: + if ( + request.META['REQUEST_METHOD'] in ['PUT', 'PATCH', 'DELETE'] + and kwargs['pk'] is None + ): raise exceptions.RequestValidationError('The object ID is missing.', 400) diff --git a/manage.py b/manage.py index 0937b3c7..51445a03 100755 --- a/manage.py +++ b/manage.py @@ -3,8 +3,7 @@ import sys if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", - "buildly.settings.base") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "buildly.settings.base") try: from django.core.management import execute_from_command_line except ImportError: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..e69de29b diff --git a/requirements/base.txt b/requirements/base.txt index abb19ec3..abc222ff 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,6 +1,6 @@ -jsonschema==3.2.0 -Django==2.2.28 +Django==3.2.24 django-filter==2.4.0 +jsonschema==3.2.0 django-health-check==3.6.1 git+https://github.com/buildlyio/django-oauth-toolkit-jwt@v0.5.2#egg=django-oauth-toolkit-jwt djangorestframework==3.11.2 @@ -11,8 +11,8 @@ futures==3.1.1 django-cors-headers==2.5.3 pyswagger==0.8.39 bravado-core==5.13.1 -drf-yasg==1.17.1 -requests==2.25.0 -aiohttp==3.7.4 +drf-yasg==1.10.2 +requests==2.31.0 +aiohttp==3.8.6 django-auth-ldap==2.1.0 stripe==2.75.0 diff --git a/requirements/test.txt b/requirements/test.txt index c98bb121..f53f87f6 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,5 +1,9 @@ -r ci.txt factory_boy==2.12.0 +<<<<<<< HEAD +ipython==8.10.0 +======= ipython==7.16.3 +>>>>>>> master ipdb==0.12.2 diff --git a/workflow/admin.py b/workflow/admin.py index 35c776e7..b288c945 100644 --- a/workflow/admin.py +++ b/workflow/admin.py @@ -1,13 +1,22 @@ from django.contrib import admin -from .models import WorkflowLevel1, WorkflowLevel2, WorkflowLevel2Sort, WorkflowTeam, WorkflowLevelStatus +from .models import ( + WorkflowLevel1, + WorkflowLevel2, + WorkflowLevel2Sort, + WorkflowTeam, + WorkflowLevelStatus, +) class WorkflowTeamAdmin(admin.ModelAdmin): list_display = ('workflow_user', 'workflowlevel1') display = 'Workflow Team' - search_fields = ('workflow_user__username', 'workflowlevel1__name', - 'workflow_user__last_name') + search_fields = ( + 'workflow_user__username', + 'workflowlevel1__name', + 'workflow_user__last_name', + ) list_filter = ('create_date',) diff --git a/workflow/migrations/0001_initial.py b/workflow/migrations/0001_initial.py index 423aaba5..9ab80a9c 100644 --- a/workflow/migrations/0001_initial.py +++ b/workflow/migrations/0001_initial.py @@ -12,39 +12,127 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( name='Internationalization', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('language', models.CharField(blank=True, max_length=100, null=True, verbose_name='Language')), + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'language', + models.CharField( + blank=True, max_length=100, null=True, verbose_name='Language' + ), + ), ('language_file', django.contrib.postgres.fields.jsonb.JSONField()), ('create_date', models.DateTimeField(blank=True, null=True)), ('edit_date', models.DateTimeField(blank=True, null=True)), ], - options={ - 'ordering': ('language',), - }, + options={'ordering': ('language',)}, ), migrations.CreateModel( name='WorkflowLevel1', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('level1_uuid', models.CharField(default=uuid.uuid4, editable=False, max_length=255, unique=True, verbose_name='WorkflowLevel1 UUID')), - ('unique_id', models.CharField(blank=True, help_text='User facing unique ID field if needed', max_length=255, null=True, verbose_name='ID')), - ('name', models.CharField(blank=True, help_text="Top level workflow can have child workflowleves, name it according to it's grouping of children", max_length=255, verbose_name='Name')), - ('description', models.TextField(blank=True, help_text='Describe how this collection of related workflows are used', max_length=765, null=True, verbose_name='Description')), - ('start_date', models.DateTimeField(blank=True, help_text='If required a time span can be associated with workflow level', null=True)), - ('end_date', models.DateTimeField(blank=True, help_text='If required a time span can be associated with workflow level', null=True)), + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'level1_uuid', + models.CharField( + default=uuid.uuid4, + editable=False, + max_length=255, + unique=True, + verbose_name='WorkflowLevel1 UUID', + ), + ), + ( + 'unique_id', + models.CharField( + blank=True, + help_text='User facing unique ID field if needed', + max_length=255, + null=True, + verbose_name='ID', + ), + ), + ( + 'name', + models.CharField( + blank=True, + help_text="Top level workflow can have child workflowleves, name it according to it's grouping of children", + max_length=255, + verbose_name='Name', + ), + ), + ( + 'description', + models.TextField( + blank=True, + help_text='Describe how this collection of related workflows are used', + max_length=765, + null=True, + verbose_name='Description', + ), + ), + ( + 'start_date', + models.DateTimeField( + blank=True, + help_text='If required a time span can be associated with workflow level', + null=True, + ), + ), + ( + 'end_date', + models.DateTimeField( + blank=True, + help_text='If required a time span can be associated with workflow level', + null=True, + ), + ), ('create_date', models.DateTimeField(blank=True, null=True)), ('edit_date', models.DateTimeField(blank=True, null=True)), ('sort', models.IntegerField(default=0)), - ('core_groups', models.ManyToManyField(blank=True, related_name='workflowlevel1s', related_query_name='workflowlevel1s', to='core.CoreGroup', verbose_name='Core groups')), - ('organization', models.ForeignKey(blank=True, help_text='Related Org to associate with', null=True, on_delete=django.db.models.deletion.CASCADE, to='core.Organization')), - ('user_access', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)), + ( + 'core_groups', + models.ManyToManyField( + blank=True, + related_name='workflowlevel1s', + related_query_name='workflowlevel1s', + to='core.CoreGroup', + verbose_name='Core groups', + ), + ), + ( + 'organization', + models.ForeignKey( + blank=True, + help_text='Related Org to associate with', + null=True, + on_delete=django.db.models.deletion.CASCADE, + to='core.Organization', + ), + ), + ( + 'user_access', + models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL), + ), ], options={ 'verbose_name': 'Workflow Level 1', @@ -55,18 +143,97 @@ class Migration(migrations.Migration): migrations.CreateModel( name='WorkflowLevel2', fields=[ - ('level2_uuid', models.UUIDField(default=uuid.uuid4, help_text='Unique ID', primary_key=True, serialize=False, verbose_name='WorkflowLevel2 UUID')), - ('description', models.TextField(blank=True, help_text='Description of the workflow level use', null=True, verbose_name='Description')), - ('name', models.CharField(help_text='Name of workflow level as it relates to workflow level 1', max_length=255, verbose_name='Name')), + ( + 'level2_uuid', + models.UUIDField( + default=uuid.uuid4, + help_text='Unique ID', + primary_key=True, + serialize=False, + verbose_name='WorkflowLevel2 UUID', + ), + ), + ( + 'description', + models.TextField( + blank=True, + help_text='Description of the workflow level use', + null=True, + verbose_name='Description', + ), + ), + ( + 'name', + models.CharField( + help_text='Name of workflow level as it relates to workflow level 1', + max_length=255, + verbose_name='Name', + ), + ), ('notes', models.TextField(blank=True, null=True)), - ('parent_workflowlevel2', models.IntegerField(blank=True, default=0, help_text='Workflow level 2 can relate to another workflow level 2 creating multiple levels of relationships', verbose_name='Parent')), - ('short_name', models.CharField(blank=True, help_text='Shortened name autogenerated', max_length=20, null=True, verbose_name='Code')), - ('create_date', models.DateTimeField(blank=True, null=True, verbose_name='Date Created')), - ('edit_date', models.DateTimeField(blank=True, null=True, verbose_name='Last Edit Date')), - ('start_date', models.DateTimeField(blank=True, null=True, verbose_name='Start Date')), - ('end_date', models.DateTimeField(blank=True, null=True, verbose_name='End Date')), - ('core_groups', models.ManyToManyField(blank=True, related_name='workflowlevel2s', related_query_name='workflowlevel2s', to='core.CoreGroup', verbose_name='Core groups')), - ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='workflowlevel2', to=settings.AUTH_USER_MODEL)), + ( + 'parent_workflowlevel2', + models.IntegerField( + blank=True, + default=0, + help_text='Workflow level 2 can relate to another workflow level 2 creating multiple levels of relationships', + verbose_name='Parent', + ), + ), + ( + 'short_name', + models.CharField( + blank=True, + help_text='Shortened name autogenerated', + max_length=20, + null=True, + verbose_name='Code', + ), + ), + ( + 'create_date', + models.DateTimeField( + blank=True, null=True, verbose_name='Date Created' + ), + ), + ( + 'edit_date', + models.DateTimeField( + blank=True, null=True, verbose_name='Last Edit Date' + ), + ), + ( + 'start_date', + models.DateTimeField( + blank=True, null=True, verbose_name='Start Date' + ), + ), + ( + 'end_date', + models.DateTimeField( + blank=True, null=True, verbose_name='End Date' + ), + ), + ( + 'core_groups', + models.ManyToManyField( + blank=True, + related_name='workflowlevel2s', + related_query_name='workflowlevel2s', + to='core.CoreGroup', + verbose_name='Core groups', + ), + ), + ( + 'created_by', + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='workflowlevel2', + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ 'verbose_name': 'Workflow Level 2', @@ -77,11 +244,26 @@ class Migration(migrations.Migration): migrations.CreateModel( name='WorkflowLevelStatus', fields=[ - ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ( + 'uuid', + models.UUIDField( + default=uuid.uuid4, primary_key=True, serialize=False + ), + ), ('order', models.PositiveSmallIntegerField(default=0)), - ('name', models.CharField(help_text='Name of WorkflowLevelStatus', max_length=255, verbose_name='Name')), + ( + 'name', + models.CharField( + help_text='Name of WorkflowLevelStatus', + max_length=255, + verbose_name='Name', + ), + ), ('short_name', models.SlugField(max_length=63, unique=True)), - ('create_date', models.DateTimeField(default=django.utils.timezone.now)), + ( + 'create_date', + models.DateTimeField(default=django.utils.timezone.now), + ), ('edit_date', models.DateTimeField(auto_now=True)), ], options={ @@ -93,28 +275,108 @@ class Migration(migrations.Migration): migrations.CreateModel( name='WorkflowLevelType', fields=[ - ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('name', models.CharField(help_text='Name of workflow2 type', max_length=255, verbose_name='Name')), - ('create_date', models.DateTimeField(default=django.utils.timezone.now)), + ( + 'uuid', + models.UUIDField( + default=uuid.uuid4, primary_key=True, serialize=False + ), + ), + ( + 'name', + models.CharField( + help_text='Name of workflow2 type', + max_length=255, + verbose_name='Name', + ), + ), + ( + 'create_date', + models.DateTimeField(default=django.utils.timezone.now), + ), ('edit_date', models.DateTimeField(auto_now=True)), ], - options={ - 'ordering': ('create_date',), - }, + options={'ordering': ('create_date',)}, ), migrations.CreateModel( name='WorkflowTeam', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('team_uuid', models.CharField(default=uuid.uuid4, editable=False, max_length=255, unique=True, verbose_name='WorkflowLevel1 UUID')), - ('start_date', models.DateTimeField(blank=True, help_text='If required a time span can be associated with workflow level access', null=True)), - ('end_date', models.DateTimeField(blank=True, help_text='If required a time span can be associated with workflow level access expiration', null=True)), - ('status', models.CharField(blank=True, help_text='Active status of access', max_length=255, null=True)), + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'team_uuid', + models.CharField( + default=uuid.uuid4, + editable=False, + max_length=255, + unique=True, + verbose_name='WorkflowLevel1 UUID', + ), + ), + ( + 'start_date', + models.DateTimeField( + blank=True, + help_text='If required a time span can be associated with workflow level access', + null=True, + ), + ), + ( + 'end_date', + models.DateTimeField( + blank=True, + help_text='If required a time span can be associated with workflow level access expiration', + null=True, + ), + ), + ( + 'status', + models.CharField( + blank=True, + help_text='Active status of access', + max_length=255, + null=True, + ), + ), ('create_date', models.DateTimeField(blank=True, null=True)), ('edit_date', models.DateTimeField(blank=True, null=True)), - ('role', models.ForeignKey(blank=True, help_text='Type of access via related group', null=True, on_delete=django.db.models.deletion.CASCADE, to='auth.Group')), - ('workflow_user', models.ForeignKey(blank=True, help_text='User with access/permissions to related workflowlevels', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='auth_approving', to=settings.AUTH_USER_MODEL)), - ('workflowlevel1', models.ForeignKey(blank=True, help_text='Related workflowlevel 1', null=True, on_delete=django.db.models.deletion.CASCADE, to='workflow.WorkflowLevel1')), + ( + 'role', + models.ForeignKey( + blank=True, + help_text='Type of access via related group', + null=True, + on_delete=django.db.models.deletion.CASCADE, + to='auth.Group', + ), + ), + ( + 'workflow_user', + models.ForeignKey( + blank=True, + help_text='User with access/permissions to related workflowlevels', + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='auth_approving', + to=settings.AUTH_USER_MODEL, + ), + ), + ( + 'workflowlevel1', + models.ForeignKey( + blank=True, + help_text='Related workflowlevel 1', + null=True, + on_delete=django.db.models.deletion.CASCADE, + to='workflow.WorkflowLevel1', + ), + ), ], options={ 'verbose_name': 'Workflow Team', @@ -125,13 +387,50 @@ class Migration(migrations.Migration): migrations.CreateModel( name='WorkflowLevel2Sort', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('workflowlevel2_pk', models.UUIDField(default='00000000-0000-4000-8000-000000000000', verbose_name='UUID to be Sorted')), - ('sort_array', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Sorted JSON array of workflow levels', null=True)), + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'workflowlevel2_pk', + models.UUIDField( + default='00000000-0000-4000-8000-000000000000', + verbose_name='UUID to be Sorted', + ), + ), + ( + 'sort_array', + django.contrib.postgres.fields.jsonb.JSONField( + blank=True, + help_text='Sorted JSON array of workflow levels', + null=True, + ), + ), ('create_date', models.DateTimeField(blank=True, null=True)), ('edit_date', models.DateTimeField(blank=True, null=True)), - ('workflowlevel1', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='workflow.WorkflowLevel1')), - ('workflowlevel2_parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='workflow.WorkflowLevel2')), + ( + 'workflowlevel1', + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to='workflow.WorkflowLevel1', + ), + ), + ( + 'workflowlevel2_parent', + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to='workflow.WorkflowLevel2', + ), + ), ], options={ 'verbose_name': 'Workflow Level Sort', @@ -142,16 +441,34 @@ class Migration(migrations.Migration): migrations.AddField( model_name='workflowlevel2', name='status', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='workflowlevel2s', to='workflow.WorkflowLevelStatus'), + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='workflowlevel2s', + to='workflow.WorkflowLevelStatus', + ), ), migrations.AddField( model_name='workflowlevel2', name='type', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='workflowlevel2s', to='workflow.WorkflowLevelType'), + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='workflowlevel2s', + to='workflow.WorkflowLevelType', + ), ), migrations.AddField( model_name='workflowlevel2', name='workflowlevel1', - field=models.ForeignKey(help_text='Primary or parent Workflow', on_delete=django.db.models.deletion.CASCADE, related_name='workflowlevel2', to='workflow.WorkflowLevel1', verbose_name='Workflow Level 1'), + field=models.ForeignKey( + help_text='Primary or parent Workflow', + on_delete=django.db.models.deletion.CASCADE, + related_name='workflowlevel2', + to='workflow.WorkflowLevel1', + verbose_name='Workflow Level 1', + ), ), ] diff --git a/workflow/models.py b/workflow/models.py index d368c942..09ba093e 100644 --- a/workflow/models.py +++ b/workflow/models.py @@ -5,6 +5,7 @@ from django.contrib.auth.models import Group from django.contrib.postgres.fields import JSONField from django.core.exceptions import ValidationError + try: from django.utils import timezone except ImportError: @@ -41,13 +42,15 @@ class WorkflowLevelType(models.Model): edit_date = models.DateTimeField(auto_now=True) class Meta: - ordering = ('create_date', ) + ordering = ('create_date',) class WorkflowLevelStatus(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4) order = models.PositiveSmallIntegerField(default=0) - name = models.CharField("Name", max_length=255, help_text="Name of WorkflowLevelStatus") + name = models.CharField( + "Name", max_length=255, help_text="Name of WorkflowLevelStatus" + ) short_name = models.SlugField(max_length=63, unique=True) create_date = models.DateTimeField(default=timezone.now) edit_date = models.DateTimeField(auto_now=True) @@ -56,24 +59,67 @@ def __str__(self): return self.name class Meta: - ordering = ('order', ) + ordering = ('order',) verbose_name = "Workflow Level Status" verbose_name_plural = "Workflow Level Statuses" class WorkflowLevel1(models.Model): - level1_uuid = models.CharField(max_length=255, editable=False, verbose_name='WorkflowLevel1 UUID', default=uuid.uuid4, unique=True) - unique_id = models.CharField("ID", max_length=255, blank=True, null=True, help_text="User facing unique ID field if needed") - name = models.CharField("Name", max_length=255, blank=True, help_text="Top level workflow can have child workflowleves, name it according to it's grouping of children") - organization = models.ForeignKey(Organization, blank=True, on_delete=models.CASCADE, null=True, help_text='Related Org to associate with') - description = models.TextField("Description", max_length=765, null=True, blank=True, help_text='Describe how this collection of related workflows are used') + level1_uuid = models.CharField( + max_length=255, + editable=False, + verbose_name='WorkflowLevel1 UUID', + default=uuid.uuid4, + unique=True, + ) + unique_id = models.CharField( + "ID", + max_length=255, + blank=True, + null=True, + help_text="User facing unique ID field if needed", + ) + name = models.CharField( + "Name", + max_length=255, + blank=True, + help_text="Top level workflow can have child workflowleves, name it according to it's grouping of children", + ) + organization = models.ForeignKey( + Organization, + blank=True, + on_delete=models.CASCADE, + null=True, + help_text='Related Org to associate with', + ) + description = models.TextField( + "Description", + max_length=765, + null=True, + blank=True, + help_text='Describe how this collection of related workflows are used', + ) user_access = models.ManyToManyField(CoreUser, blank=True) - start_date = models.DateTimeField(null=True, blank=True, help_text='If required a time span can be associated with workflow level') - end_date = models.DateTimeField(null=True, blank=True, help_text='If required a time span can be associated with workflow level') + start_date = models.DateTimeField( + null=True, + blank=True, + help_text='If required a time span can be associated with workflow level', + ) + end_date = models.DateTimeField( + null=True, + blank=True, + help_text='If required a time span can be associated with workflow level', + ) create_date = models.DateTimeField(null=True, blank=True) edit_date = models.DateTimeField(null=True, blank=True) sort = models.IntegerField(default=0) # sort array - core_groups = models.ManyToManyField(CoreGroup, verbose_name='Core groups', blank=True, related_name='workflowlevel1s', related_query_name='workflowlevel1s') + core_groups = models.ManyToManyField( + CoreGroup, + verbose_name='Core groups', + blank=True, + related_name='workflowlevel1s', + related_query_name='workflowlevel1s', + ) class Meta: ordering = ('name',) @@ -100,21 +146,76 @@ def __str__(self): class WorkflowLevel2(models.Model): - level2_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, verbose_name='WorkflowLevel2 UUID', help_text="Unique ID") - description = models.TextField("Description", blank=True, null=True, help_text="Description of the workflow level use") - name = models.CharField("Name", max_length=255, help_text="Name of workflow level as it relates to workflow level 1") + level2_uuid = models.UUIDField( + primary_key=True, + default=uuid.uuid4, + verbose_name='WorkflowLevel2 UUID', + help_text="Unique ID", + ) + description = models.TextField( + "Description", + blank=True, + null=True, + help_text="Description of the workflow level use", + ) + name = models.CharField( + "Name", + max_length=255, + help_text="Name of workflow level as it relates to workflow level 1", + ) notes = models.TextField(blank=True, null=True) - parent_workflowlevel2 = models.IntegerField("Parent", default=0, blank=True, help_text="Workflow level 2 can relate to another workflow level 2 creating multiple levels of relationships") - short_name = models.CharField("Code", max_length=20, blank=True, null=True, help_text="Shortened name autogenerated") - workflowlevel1 = models.ForeignKey(WorkflowLevel1, verbose_name="Workflow Level 1", on_delete=models.CASCADE, related_name="workflowlevel2", help_text="Primary or parent Workflow") + parent_workflowlevel2 = models.IntegerField( + "Parent", + default=0, + blank=True, + help_text="Workflow level 2 can relate to another workflow level 2 creating multiple levels of relationships", + ) + short_name = models.CharField( + "Code", + max_length=20, + blank=True, + null=True, + help_text="Shortened name autogenerated", + ) + workflowlevel1 = models.ForeignKey( + WorkflowLevel1, + verbose_name="Workflow Level 1", + on_delete=models.CASCADE, + related_name="workflowlevel2", + help_text="Primary or parent Workflow", + ) create_date = models.DateTimeField("Date Created", null=True, blank=True) - created_by = models.ForeignKey(CoreUser, related_name='workflowlevel2', null=True, blank=True, on_delete=models.SET_NULL) + created_by = models.ForeignKey( + CoreUser, + related_name='workflowlevel2', + null=True, + blank=True, + on_delete=models.SET_NULL, + ) edit_date = models.DateTimeField("Last Edit Date", null=True, blank=True) - core_groups = models.ManyToManyField(CoreGroup, verbose_name='Core groups', blank=True, related_name='workflowlevel2s', related_query_name='workflowlevel2s') + core_groups = models.ManyToManyField( + CoreGroup, + verbose_name='Core groups', + blank=True, + related_name='workflowlevel2s', + related_query_name='workflowlevel2s', + ) start_date = models.DateTimeField("Start Date", null=True, blank=True) end_date = models.DateTimeField("End Date", null=True, blank=True) - type = models.ForeignKey(WorkflowLevelType, null=True, blank=True, on_delete=models.SET_NULL, related_name='workflowlevel2s') - status = models.ForeignKey(WorkflowLevelStatus, null=True, blank=True, on_delete=models.SET_NULL, related_name='workflowlevel2s') + type = models.ForeignKey( + WorkflowLevelType, + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='workflowlevel2s', + ) + status = models.ForeignKey( + WorkflowLevelStatus, + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='workflowlevel2s', + ) class Meta: ordering = ('name',) @@ -141,13 +242,49 @@ class WorkflowTeam(models.Model): WorkflowTeam defines m2m relations between CoreUser and Workflowlevel1. It also defines a role for this relationship (as a fk to Group instance). """ - team_uuid = models.CharField(max_length=255, editable=False, verbose_name='WorkflowLevel1 UUID', default=uuid.uuid4, unique=True) - workflow_user = models.ForeignKey(CoreUser, blank=True, null=True, on_delete=models.CASCADE, related_name="auth_approving", help_text='User with access/permissions to related workflowlevels') - workflowlevel1 = models.ForeignKey(WorkflowLevel1, null=True, on_delete=models.CASCADE, blank=True, help_text='Related workflowlevel 1') - start_date = models.DateTimeField(null=True, blank=True, help_text='If required a time span can be associated with workflow level access') - end_date = models.DateTimeField(null=True, blank=True, help_text='If required a time span can be associated with workflow level access expiration') - status = models.CharField(max_length=255, null=True, blank=True, help_text='Active status of access') - role = models.ForeignKey(Group, null=True, blank=True, on_delete=models.CASCADE, help_text='Type of access via related group') + + team_uuid = models.CharField( + max_length=255, + editable=False, + verbose_name='WorkflowLevel1 UUID', + default=uuid.uuid4, + unique=True, + ) + workflow_user = models.ForeignKey( + CoreUser, + blank=True, + null=True, + on_delete=models.CASCADE, + related_name="auth_approving", + help_text='User with access/permissions to related workflowlevels', + ) + workflowlevel1 = models.ForeignKey( + WorkflowLevel1, + null=True, + on_delete=models.CASCADE, + blank=True, + help_text='Related workflowlevel 1', + ) + start_date = models.DateTimeField( + null=True, + blank=True, + help_text='If required a time span can be associated with workflow level access', + ) + end_date = models.DateTimeField( + null=True, + blank=True, + help_text='If required a time span can be associated with workflow level access expiration', + ) + status = models.CharField( + max_length=255, null=True, blank=True, help_text='Active status of access' + ) + role = models.ForeignKey( + Group, + null=True, + blank=True, + on_delete=models.CASCADE, + help_text='Type of access via related group', + ) create_date = models.DateTimeField(null=True, blank=True) edit_date = models.DateTimeField(null=True, blank=True) @@ -177,10 +314,18 @@ def organization(self) -> Union[Organization, None]: class WorkflowLevel2Sort(models.Model): - workflowlevel1 = models.ForeignKey(WorkflowLevel1, null=True, on_delete=models.CASCADE, blank=True) - workflowlevel2_parent = models.ForeignKey(WorkflowLevel2, on_delete=models.CASCADE, null=True, blank=True) - workflowlevel2_pk = models.UUIDField("UUID to be Sorted", default='00000000-0000-4000-8000-000000000000') - sort_array = JSONField(null=True, blank=True, help_text="Sorted JSON array of workflow levels") + workflowlevel1 = models.ForeignKey( + WorkflowLevel1, null=True, on_delete=models.CASCADE, blank=True + ) + workflowlevel2_parent = models.ForeignKey( + WorkflowLevel2, on_delete=models.CASCADE, null=True, blank=True + ) + workflowlevel2_pk = models.UUIDField( + "UUID to be Sorted", default='00000000-0000-4000-8000-000000000000' + ) + sort_array = JSONField( + null=True, blank=True, help_text="Sorted JSON array of workflow levels" + ) create_date = models.DateTimeField(null=True, blank=True) edit_date = models.DateTimeField(null=True, blank=True) diff --git a/workflow/pagination.py b/workflow/pagination.py index 929d49b5..9b31cd5f 100644 --- a/workflow/pagination.py +++ b/workflow/pagination.py @@ -1,4 +1,8 @@ -from rest_framework.pagination import CursorPagination, PageNumberPagination, LimitOffsetPagination +from rest_framework.pagination import ( + CursorPagination, + PageNumberPagination, + LimitOffsetPagination, +) class StandardResultsSetPagination(PageNumberPagination): diff --git a/workflow/permissions.py b/workflow/permissions.py index 9a5eaf5a..dd7d8824 100644 --- a/workflow/permissions.py +++ b/workflow/permissions.py @@ -20,20 +20,21 @@ class IsSuperUserOrReadOnly(permissions.BasePermission): def has_permission(self, request, view): return ( - request.method in permissions.SAFE_METHODS or - request.user and - request.user.is_authenticated and - request.user.is_superuser + request.method in permissions.SAFE_METHODS + or request.user + and request.user.is_authenticated + and request.user.is_superuser ) class CoreGroupsPermissions(permissions.BasePermission): - def _get_workflowlevel(self, view, request_data, field_name): wflvl_serializer = view.serializer_class().get_fields()[field_name] # Check if the field is Many-To-Many or not - if wflvl_serializer.__class__ == ManyRelatedField and isinstance(request_data, QueryDict): + if wflvl_serializer.__class__ == ManyRelatedField and isinstance( + request_data, QueryDict + ): primitive_value = request_data.getlist(field_name) else: primitive_value = request_data.get(field_name) @@ -57,23 +58,36 @@ def has_permission(self, request, view): return True # TODO: check if we can optimize following query using 'through' M2M Models - user_groups = request.user.core_groups.prefetch_related('workflowlevel1s', 'workflowlevel2s') + user_groups = request.user.core_groups.prefetch_related( + 'workflowlevel1s', 'workflowlevel2s' + ) # sort up permissions into more convenient way (default is read-only '0100') viewonly_display_permissions = '{0:04b}'.format(PERMISSIONS_VIEW_ONLY) - global_permissions, org_permissions = viewonly_display_permissions, viewonly_display_permissions + global_permissions, org_permissions = ( + viewonly_display_permissions, + viewonly_display_permissions, + ) wl1_permissions = defaultdict(lambda: viewonly_display_permissions) wl2_permissions = defaultdict(lambda: viewonly_display_permissions) for group in user_groups: if group.is_global: - global_permissions = merge_permissions(global_permissions, group.display_permissions) + global_permissions = merge_permissions( + global_permissions, group.display_permissions + ) elif group.is_org_level: - org_permissions = merge_permissions(org_permissions, group.display_permissions) + org_permissions = merge_permissions( + org_permissions, group.display_permissions + ) else: for wl1 in group.workflowlevel1s.all(): - wl1_permissions[wl1.pk] = merge_permissions(wl1_permissions[wl1.pk], group.display_permissions) + wl1_permissions[wl1.pk] = merge_permissions( + wl1_permissions[wl1.pk], group.display_permissions + ) for wl2 in group.workflowlevel2s.all(): - wl2_permissions[wl2.pk] = merge_permissions(wl2_permissions[wl2.pk], group.display_permissions) + wl2_permissions[wl2.pk] = merge_permissions( + wl2_permissions[wl2.pk], group.display_permissions + ) action = view.action if has_permission(global_permissions, action): @@ -90,7 +104,9 @@ def has_permission(self, request, view): if data.get('workflowlevel1'): wflvl1 = self._get_workflowlevel(view, data, 'workflowlevel1') else: - wflvl1 = self._get_workflowlevel1_from_level2(data['workflowlevel2']) + wflvl1 = self._get_workflowlevel1_from_level2( + data['workflowlevel2'] + ) if not wflvl1: return False @@ -114,16 +130,19 @@ def _queryset(self, view): :param view: :return: QuerySet """ - assert hasattr(view, 'get_queryset') or getattr(view, 'queryset', None) is not None, ( + assert ( + hasattr(view, 'get_queryset') or getattr(view, 'queryset', None) is not None + ), ( 'Cannot apply {} on a view that does not set ' '`.queryset` or have a `.get_queryset()` method.' - ).format(self.__class__.__name__) + ).format( + self.__class__.__name__ + ) if hasattr(view, 'get_queryset'): queryset = view.get_queryset() - assert queryset is not None, ( - '{}.get_queryset() returned None'.format( - view.__class__.__name__) + assert queryset is not None, '{}.get_queryset() returned None'.format( + view.__class__.__name__ ) return queryset @@ -147,7 +166,9 @@ def has_object_permission(self, request, view, obj): # Permissions on WorkflowLevel1 itself are defined by Org-level permissions groups = request.user.core_groups.filter(is_org_level=True) elif hasattr(obj, 'workflowlevel1'): - groups = request.user.core_groups.all().intersection(obj.workflowlevel1.core_groups.all()) + groups = request.user.core_groups.all().intersection( + obj.workflowlevel1.core_groups.all() + ) else: return True diff --git a/workflow/serializers.py b/workflow/serializers.py index 9b88273d..6e82efc2 100755 --- a/workflow/serializers.py +++ b/workflow/serializers.py @@ -3,7 +3,6 @@ class WorkflowLevel1Serializer(serializers.ModelSerializer): - class Meta: model = wfm.WorkflowLevel1 fields = '__all__' @@ -34,30 +33,26 @@ class Meta: class InternationalizationSerializer(serializers.ModelSerializer): - class Meta: model = wfm.Internationalization fields = '__all__' class WorkflowLevel2NameSerializer(serializers.ModelSerializer): - class Meta: model = wfm.WorkflowLevel2 fields = ('level2_uuid', 'name') - read_only_fields = ('level2_uuid', ) + read_only_fields = ('level2_uuid',) class WorkflowLevel2SortSerializer(serializers.ModelSerializer): - class Meta: model = wfm.WorkflowLevel2Sort fields = '__all__' - read_only_fields = ('level2_uuid', ) + read_only_fields = ('level2_uuid',) class WorkflowTeamSerializer(serializers.ModelSerializer): - class Meta: model = wfm.WorkflowTeam fields = '__all__' diff --git a/workflow/tests/test_internationalizationview.py b/workflow/tests/test_internationalizationview.py index 3cc0667f..a1433a72 100644 --- a/workflow/tests/test_internationalizationview.py +++ b/workflow/tests/test_internationalizationview.py @@ -53,7 +53,7 @@ def test_create_internationalization_superuser(self): data = { 'language': 'pt-BR', - 'language_file': '{"name": "Nome", "gender": "Gênero"}' + 'language_file': '{"name": "Nome", "gender": "Gênero"}', } request = self.factory.post('/internationalization/', data) request.user = self.core_user @@ -69,7 +69,7 @@ def test_create_internationalization_normaluser(self): """ data = { 'language': 'pt-BR', - 'language_file': '{"name": "Nome", "gender": "Gênero"}' + 'language_file': '{"name": "Nome", "gender": "Gênero"}', } request = self.factory.post('/internationalization/', data) request.user = self.core_user @@ -100,8 +100,7 @@ def test_retrieve_internationalization_superuser(self): self.core_user.save() inter = factories.Internationalization() - request = self.factory.get('/internationalization/{}'.format( - inter.id)) + request = self.factory.get('/internationalization/{}'.format(inter.id)) request.user = self.core_user view = InternationalizationViewSet.as_view({'get': 'retrieve'}) response = view(request, pk=inter.pk) @@ -114,8 +113,7 @@ def test_retrieve_internationalization_normaluser(self): """ inter = factories.Internationalization() - request = self.factory.get('/internationalization/{}'.format( - inter.id)) + request = self.factory.get('/internationalization/{}'.format(inter.id)) request.user = self.core_user view = InternationalizationViewSet.as_view({'get': 'retrieve'}) response = view(request, pk=inter.pk) @@ -133,9 +131,7 @@ def test_update_unexisting_internationalization(self): self.core_user.is_superuser = True self.core_user.save() - data = { - 'language': 'pt-BR', - } + data = {'language': 'pt-BR'} request = self.factory.post('/internationalization/', data) request.user = self.core_user view = InternationalizationViewSet.as_view({'post': 'update'}) @@ -154,7 +150,7 @@ def test_update_internationalization_superuser(self): data = { 'language': 'pt-BR', - 'language_file': '{"name": "Nome", "gender": "Gênero"}' + 'language_file': '{"name": "Nome", "gender": "Gênero"}', } request = self.factory.post('/internationalization/', data) request.user = self.core_user @@ -170,9 +166,7 @@ def test_update_internationalization_normaluser(self): """ inter = factories.Internationalization() - data = { - 'language': 'pt-BR', - } + data = {'language': 'pt-BR'} request = self.factory.post('/internationalization/', data) request.user = self.core_user view = InternationalizationViewSet.as_view({'post': 'update'}) @@ -215,7 +209,9 @@ def test_delete_internationalization_superuser(self): self.assertEqual(response.status_code, 204) self.assertRaises( Internationalization.DoesNotExist, - Internationalization.objects.get, pk=inter.pk) + Internationalization.objects.get, + pk=inter.pk, + ) def test_delete_internationalization_normaluser(self): """ diff --git a/workflow/tests/test_serializers.py b/workflow/tests/test_serializers.py index aa48087d..84d1bcfa 100644 --- a/workflow/tests/test_serializers.py +++ b/workflow/tests/test_serializers.py @@ -8,23 +8,24 @@ def test_workflow_level2_serializer(request_factory, wfl2): request = request_factory.get('') serializer = WorkflowLevel2Serializer(wfl2, context={'request': request}) data = serializer.data - keys = ["id", - "level2_uuid", - "description", - "name", - "notes", - "parent_workflowlevel2", - "short_name", - "create_date", - "edit_date", - "start_date", - "end_date", - "workflowlevel1", - "created_by", - "type", - "core_groups", - "status", - ] + keys = [ + "id", + "level2_uuid", + "description", + "name", + "notes", + "parent_workflowlevel2", + "short_name", + "create_date", + "edit_date", + "start_date", + "end_date", + "workflowlevel1", + "created_by", + "type", + "core_groups", + "status", + ] assert set(data.keys()) == set(keys) assert data["id"] == data["level2_uuid"] @@ -34,11 +35,6 @@ def test_workflow_level_type_serializer(request_factory, wfl_type): request = request_factory.get('') serializer = WorkflowLevelTypeSerializer(wfl_type, context={'request': request}) data = serializer.data - keys = ["id", - "uuid", - "name", - "create_date", - "edit_date", - ] + keys = ["id", "uuid", "name", "create_date", "edit_date"] assert set(data.keys()) == set(keys) assert data["id"] == data["uuid"] diff --git a/workflow/tests/test_workflowlevel1view.py b/workflow/tests/test_workflowlevel1view.py index 4f98b898..ac0bfd33 100644 --- a/workflow/tests/test_workflowlevel1view.py +++ b/workflow/tests/test_workflowlevel1view.py @@ -6,8 +6,12 @@ import factories from rest_framework.reverse import reverse from rest_framework.test import APIRequestFactory -from core.models import PERMISSIONS_ORG_ADMIN, PERMISSIONS_WORKFLOW_ADMIN, PERMISSIONS_WORKFLOW_TEAM, \ - PERMISSIONS_VIEW_ONLY +from core.models import ( + PERMISSIONS_ORG_ADMIN, + PERMISSIONS_WORKFLOW_ADMIN, + PERMISSIONS_WORKFLOW_TEAM, + PERMISSIONS_VIEW_ONLY, +) from workflow.models import WorkflowLevel1 from ..views import WorkflowLevel1ViewSet @@ -37,9 +41,12 @@ def test_list_workflowlevel1_superuser(self): def test_list_workflowlevel1_superuser_and_org_admin(self): wflvl1 = factories.WorkflowLevel1() wflvl2 = factories.WorkflowLevel2(workflowlevel1=wflvl1) - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) self.core_user.is_staff = True @@ -55,12 +62,14 @@ def test_list_workflowlevel1_superuser_and_org_admin(self): self.assertEqual(response.data[0]['name'], wflvl1.name) def test_list_workflowlevel1_org_admin(self): - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) wflvl2 = factories.WorkflowLevel2(workflowlevel1=wflvl1) - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) request = self.factory.get(reverse('workflowlevel1-list')) @@ -72,11 +81,12 @@ def test_list_workflowlevel1_org_admin(self): self.assertEqual(response.data[0]['name'], wflvl1.name) def test_list_workflowlevel1_program_admin(self): - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) wflvl2 = factories.WorkflowLevel2(workflowlevel1=wflvl1) - group_wfl1_admin = factories.CoreGroup(name='Workflow Admin', permissions=PERMISSIONS_WORKFLOW_ADMIN) + group_wfl1_admin = factories.CoreGroup( + name='Workflow Admin', permissions=PERMISSIONS_WORKFLOW_ADMIN + ) wflvl1.core_groups.add(group_wfl1_admin) self.core_user.core_groups.add(group_wfl1_admin) @@ -89,18 +99,19 @@ def test_list_workflowlevel1_program_admin(self): self.assertEqual(response.data[0]['name'], wflvl1.name) def test_list_filter_workflowlevel1_program_admin(self): - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) wflvl1_2 = factories.WorkflowLevel1( name='Population Health Initiative', - organization=self.core_user.organization) + organization=self.core_user.organization, + ) - group_wfl1_admin = factories.CoreGroup(name='Workflow Admin', permissions=PERMISSIONS_WORKFLOW_ADMIN) + group_wfl1_admin = factories.CoreGroup( + name='Workflow Admin', permissions=PERMISSIONS_WORKFLOW_ADMIN + ) wflvl1.core_groups.add(group_wfl1_admin) wflvl1_2.core_groups.add(group_wfl1_admin) self.core_user.core_groups.add(group_wfl1_admin) - request = self.factory.get( '{}?name={}'.format(reverse('workflowlevel1-list'), wflvl1.name) ) @@ -113,11 +124,12 @@ def test_list_filter_workflowlevel1_program_admin(self): self.assertNotEqual(response.data[0]['name'], wflvl1_2.name) def test_list_workflowlevel1_program_team(self): - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) - group_wf_team = factories.CoreGroup(name='WF Team', - permissions=PERMISSIONS_WORKFLOW_TEAM, - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF Team', + permissions=PERMISSIONS_WORKFLOW_TEAM, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1.core_groups.add(group_wf_team) @@ -131,9 +143,11 @@ def test_list_workflowlevel1_program_team(self): def test_list_workflowlevel1_normal_user_same_org(self): wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) - group_wf_team = factories.CoreGroup(name='WF View Only', - permissions=PERMISSIONS_VIEW_ONLY, - organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF View Only', + permissions=PERMISSIONS_VIEW_ONLY, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1.core_groups.add(group_wf_team) @@ -144,19 +158,27 @@ def test_list_workflowlevel1_normal_user_same_org(self): self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data), 1) - @patch('workflow.pagination.DefaultCursorPagination.page_size', new_callable=PropertyMock) + @patch( + 'workflow.pagination.DefaultCursorPagination.page_size', + new_callable=PropertyMock, + ) def test_list_workflowlevel1_pagination(self, page_size_mock): """ For page_size 1 and pagination true, list wfl1 endpoint should return 1 wfl1 for each page""" # set page_size =1 page_size_mock.return_value = 1 wfl1_1 = factories.WorkflowLevel1( - name='1. wfl', organization=self.core_user.organization) + name='1. wfl', organization=self.core_user.organization + ) wfl1_2 = factories.WorkflowLevel1( - name='2. wfl', organization=self.core_user.organization) - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + name='2. wfl', organization=self.core_user.organization + ) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) request = self.factory.get('?paginate=true') @@ -172,8 +194,7 @@ def test_list_workflowlevel1_pagination(self, page_size_mock): m = re.search('=(.*)&', response.data['next']) cursor = m.group(1) - request = self.factory.get('?cursor={}&paginate=true'.format( - cursor)) + request = self.factory.get('?cursor={}&paginate=true'.format(cursor)) request.user = self.core_user view = WorkflowLevel1ViewSet.as_view({'get': 'list'}) response = view(request) @@ -195,7 +216,7 @@ def test_create_workflowlevel1_superuser(self): data = { 'name': 'Save the Children', - 'organization': self.core_user.organization.pk + 'organization': self.core_user.organization.pk, } request = self.factory.post(reverse('workflowlevel1-list'), data) request.user = self.core_user @@ -210,9 +231,12 @@ def test_create_workflowlevel1_superuser(self): self.assertEqual(wflvl1.user_access.first(), self.core_user) def test_create_workflowlevel1_org_admin(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) data = {'name': 'Save the Children'} @@ -238,20 +262,25 @@ def test_create_workflowlevel1_normal_user(self): self.assertEqual(response.status_code, 403) def test_create_workflowlevel1_uuid_is_self_generated(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) data = { 'name': 'Save the Children', - 'level1_uuid': '75e4c912-4149-11e8-842f-0ed5f89f718b' + 'level1_uuid': '75e4c912-4149-11e8-842f-0ed5f89f718b', } request = self.factory.post(reverse('workflowlevel1-list'), data) request.user = self.core_user view = WorkflowLevel1ViewSet.as_view({'post': 'create'}) response = view(request) self.assertEqual(response.status_code, 201) - self.assertNotEqual(response.data['level1_uuid'], '75e4c912-4149-11e8-842f-0ed5f89f718b') + self.assertNotEqual( + response.data['level1_uuid'], '75e4c912-4149-11e8-842f-0ed5f89f718b' + ) class WorkflowLevel1UpdateViewsTest(TestCase): @@ -260,15 +289,16 @@ def setUp(self): self.core_user = factories.CoreUser() def test_update_unexisting_workflowlevel1(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) data = {'salary': '10'} - request = self.factory.put( - reverse('workflowlevel1-detail', args=(288,)), data - ) + request = self.factory.put(reverse('workflowlevel1-detail', args=(288,)), data) request.user = self.core_user view = WorkflowLevel1ViewSet.as_view({'put': 'update'}) response = view(request, pk=288) @@ -293,9 +323,12 @@ def test_update_workflowlevel1_superuser(self): self.assertEqual(wflvl1.name, data['name']) def test_update_workflowlevel1_org_admin(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) @@ -312,13 +345,17 @@ def test_update_workflowlevel1_org_admin(self): self.assertEqual(wflvl1.name, data['name']) def test_update_workflowlevel1_different_org_admin(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) wflvl1 = factories.WorkflowLevel1( - organization=factories.Organization(name='Other Org')) + organization=factories.Organization(name='Other Org') + ) data = {'name': 'Save the Lennons'} request = self.factory.put( reverse('workflowlevel1-detail', args=(wflvl1.pk,)), data @@ -329,15 +366,22 @@ def test_update_workflowlevel1_different_org_admin(self): self.assertEqual(response.status_code, 403) def test_update_workflowlevel1_program_admin(self): - wfl1 = factories.WorkflowLevel1.create(name='Save the Children', organization=self.core_user.organization) + wfl1 = factories.WorkflowLevel1.create( + name='Save the Children', organization=self.core_user.organization + ) - group_wf_admin = factories.CoreGroup(name='WF Admin', - permissions=PERMISSIONS_WORKFLOW_ADMIN, - organization=self.core_user.organization) + group_wf_admin = factories.CoreGroup( + name='WF Admin', + permissions=PERMISSIONS_WORKFLOW_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_admin) wfl1.core_groups.add(group_wf_admin) - request = self.factory.put(reverse('workflowlevel1-detail', args=(wfl1.pk,)), {'name': 'Save the Lennons'}) + request = self.factory.put( + reverse('workflowlevel1-detail', args=(wfl1.pk,)), + {'name': 'Save the Lennons'}, + ) request.user = self.core_user view = WorkflowLevel1ViewSet.as_view({'put': 'update'}) response = view(request, pk=wfl1.pk) @@ -345,18 +389,22 @@ def test_update_workflowlevel1_program_admin(self): self.assertEqual(response.status_code, 403) def test_update_workflowlevel1_program_admin_json(self): - wfl1 = factories.WorkflowLevel1.create(name='Save the Children', organization=self.core_user.organization) + wfl1 = factories.WorkflowLevel1.create( + name='Save the Children', organization=self.core_user.organization + ) - group_wf_admin = factories.CoreGroup(name='WF Admin', - permissions=PERMISSIONS_WORKFLOW_ADMIN, - organization=self.core_user.organization) + group_wf_admin = factories.CoreGroup( + name='WF Admin', + permissions=PERMISSIONS_WORKFLOW_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_admin) wfl1.core_groups.add(group_wf_admin) request = self.factory.put( reverse('workflowlevel1-detail', args=(wfl1.pk,)), json.dumps({'name': 'Save the Lennons'}), - content_type='application/json' + content_type='application/json', ) request.user = self.core_user view = WorkflowLevel1ViewSet.as_view({'put': 'update'}) @@ -366,13 +414,18 @@ def test_update_workflowlevel1_program_admin_json(self): def test_update_workflowlevel1_program_team(self): wflvl1 = factories.WorkflowLevel1() - group_wf_team = factories.CoreGroup(name='WF Team', - permissions=PERMISSIONS_WORKFLOW_TEAM, - organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF Team', + permissions=PERMISSIONS_WORKFLOW_TEAM, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1.core_groups.add(group_wf_team) - request = self.factory.put(reverse('workflowlevel1-detail', args=(wflvl1.pk,)), {'name': 'Save the Lennons'}) + request = self.factory.put( + reverse('workflowlevel1-detail', args=(wflvl1.pk,)), + {'name': 'Save the Lennons'}, + ) request.user = self.core_user view = WorkflowLevel1ViewSet.as_view({'put': 'update'}) response = view(request, pk=wflvl1.pk) @@ -381,14 +434,15 @@ def test_update_workflowlevel1_program_team(self): def test_update_workflowlevel1_same_org_different_program_team(self): wflvl1_other = factories.WorkflowLevel1() - group_wf_team = factories.CoreGroup(name='WF Team', - permissions=PERMISSIONS_WORKFLOW_TEAM, - organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF Team', + permissions=PERMISSIONS_WORKFLOW_TEAM, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1_other.core_groups.add(group_wf_team) - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) data = {'name': 'Save the Lennons'} request = self.factory.put( @@ -417,30 +471,35 @@ def test_delete_workflowlevel1_superuser(self): response = view(request, pk=wflvl1.pk) self.assertEqual(response.status_code, 204) self.assertRaises( - WorkflowLevel1.DoesNotExist, - WorkflowLevel1.objects.get, pk=wflvl1.pk) + WorkflowLevel1.DoesNotExist, WorkflowLevel1.objects.get, pk=wflvl1.pk + ) def test_delete_workflowlevel1_org_admin(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) request = self.factory.delete(reverse('workflowlevel1-list')) request.user = self.core_user view = WorkflowLevel1ViewSet.as_view({'delete': 'destroy'}) response = view(request, pk=wflvl1.pk) self.assertEqual(response.status_code, 204) self.assertRaises( - WorkflowLevel1.DoesNotExist, - WorkflowLevel1.objects.get, pk=wflvl1.pk) + WorkflowLevel1.DoesNotExist, WorkflowLevel1.objects.get, pk=wflvl1.pk + ) def test_delete_workflowlevel1_different_org_admin(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) org_other = factories.Organization(name='Other Org') @@ -455,9 +514,11 @@ def test_delete_workflowlevel1_different_org_admin(self): def test_delete_workflowlevel1_program_admin(self): wfl1 = factories.WorkflowLevel1(name='Save the Children') - group_wf_admin = factories.CoreGroup(name='WF Admin', - permissions=PERMISSIONS_WORKFLOW_ADMIN, - organization=self.core_user.organization) + group_wf_admin = factories.CoreGroup( + name='WF Admin', + permissions=PERMISSIONS_WORKFLOW_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_admin) wfl1.core_groups.add(group_wf_admin) @@ -491,9 +552,12 @@ def test_delete_workflowlevel1_normal_user(self): WorkflowLevel1.objects.get(pk=wflvl1.pk) def test_delete_workflowlevel1_program_admin_just_one(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) # Create a program @@ -520,7 +584,9 @@ def test_delete_workflowlevel1_program_admin_just_one(self): self.assertEqual(response.status_code, 204) self.assertRaises( WorkflowLevel1.DoesNotExist, - WorkflowLevel1.objects.get, pk=second_program_id) + WorkflowLevel1.objects.get, + pk=second_program_id, + ) WorkflowLevel1.objects.get(pk=first_program_id) @@ -549,13 +615,21 @@ def test_filter_workflowlevel1_superuser(self): def test_filter_workflowlevel1_org_admin(self): wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) - factories.WorkflowLevel1(name='Population Health Initiative', organization=self.core_user.organization) - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + factories.WorkflowLevel1( + name='Population Health Initiative', + organization=self.core_user.organization, + ) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) - request = self.factory.get('{}?name={}'.format(reverse('workflowlevel1-list'), wflvl1.name)) + request = self.factory.get( + '{}?name={}'.format(reverse('workflowlevel1-list'), wflvl1.name) + ) request.user = self.core_user view = WorkflowLevel1ViewSet.as_view({'get': 'list'}) response = view(request) diff --git a/workflow/tests/test_workflowlevel2serializers.py b/workflow/tests/test_workflowlevel2serializers.py index 5b6d98f0..eb633269 100644 --- a/workflow/tests/test_workflowlevel2serializers.py +++ b/workflow/tests/test_workflowlevel2serializers.py @@ -16,9 +16,6 @@ def test_contains_expected_fields(self): data = serializer.data - keys = [ - 'level2_uuid', - 'name' - ] + keys = ['level2_uuid', 'name'] self.assertEqual(set(data.keys()), set(keys)) diff --git a/workflow/tests/test_workflowlevel2sortview.py b/workflow/tests/test_workflowlevel2sortview.py index f05e4346..ebf5a9f2 100644 --- a/workflow/tests/test_workflowlevel2sortview.py +++ b/workflow/tests/test_workflowlevel2sortview.py @@ -3,8 +3,12 @@ from django.test import TestCase import factories from rest_framework.test import APIRequestFactory -from core.models import PERMISSIONS_ORG_ADMIN, PERMISSIONS_VIEW_ONLY, PERMISSIONS_WORKFLOW_ADMIN, \ - PERMISSIONS_WORKFLOW_TEAM +from core.models import ( + PERMISSIONS_ORG_ADMIN, + PERMISSIONS_VIEW_ONLY, + PERMISSIONS_WORKFLOW_ADMIN, + PERMISSIONS_WORKFLOW_TEAM, +) from workflow.models import WorkflowLevel2Sort from ..views import WorkflowLevel2SortViewSet @@ -13,8 +17,12 @@ class WorkflowLevel2SortListViewsTest(TestCase): def setUp(self): self.not_default_org = factories.Organization.create(name='Some Org') - wfl1_not_default_org = factories.WorkflowLevel1.create(organization=self.not_default_org) - factories.WorkflowLevel2Sort.create_batch(2, workflowlevel1=wfl1_not_default_org) + wfl1_not_default_org = factories.WorkflowLevel1.create( + organization=self.not_default_org + ) + factories.WorkflowLevel2Sort.create_batch( + 2, workflowlevel1=wfl1_not_default_org + ) self.factory = APIRequestFactory() self.core_user = factories.CoreUser() @@ -23,8 +31,7 @@ def test_list_workflowlevel2sort_superuser(self): list view should return all objs to super users """ request = self.factory.get('/workflowlevel2sort/') - request.user = factories.CoreUser.build(is_superuser=True, - is_staff=True) + request.user = factories.CoreUser.build(is_superuser=True, is_staff=True) view = WorkflowLevel2SortViewSet.as_view({'get': 'list'}) response = view(request) self.assertEqual(response.status_code, 200) @@ -35,9 +42,12 @@ def test_list_workflowlevel2sort_org_admin(self): list view should return only objs of an org to org admins """ request = self.factory.get('/workflowlevel2sort/') - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) wflvl1 = factories.WorkflowLevel1(organization=self.not_default_org) @@ -55,9 +65,11 @@ def test_list_workflowlevel2sort_program_admin(self): """ request = self.factory.get('/workflowlevel2sort/') wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) - group_wf_admin = factories.CoreGroup(name='WF Admin', - permissions=PERMISSIONS_WORKFLOW_ADMIN, - organization=self.core_user.organization) + group_wf_admin = factories.CoreGroup( + name='WF Admin', + permissions=PERMISSIONS_WORKFLOW_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_admin) wflvl1.core_groups.add(group_wf_admin) request.user = self.core_user @@ -73,9 +85,11 @@ def test_list_workflowlevel2sort_program_team(self): """ request = self.factory.get('/workflowlevel2sort/') wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) - group_wf_team = factories.CoreGroup(name='WF Team', - permissions=PERMISSIONS_WORKFLOW_TEAM, - organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF Team', + permissions=PERMISSIONS_WORKFLOW_TEAM, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1.core_groups.add(group_wf_team) request.user = self.core_user @@ -91,9 +105,11 @@ def test_list_workflowlevel2sort_view_only(self): """ request = self.factory.get('/workflowlevel2sort/') wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) - group_wf_team = factories.CoreGroup(name='WF View Only', - permissions=PERMISSIONS_VIEW_ONLY, - organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF View Only', + permissions=PERMISSIONS_VIEW_ONLY, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1.core_groups.add(group_wf_team) request.user = self.core_user @@ -117,8 +133,7 @@ def test_create_workflowlevel2_superuser(self): request = self.factory.post('/workflowlevel2sort/') wflvl1 = factories.WorkflowLevel1() - data = {'workflowlevel2_pk': uuid.uuid4(), - 'workflowlevel1': wflvl1.pk} + data = {'workflowlevel2_pk': uuid.uuid4(), 'workflowlevel1': wflvl1.pk} request = self.factory.post('/workflowlevel2/', data) request.user = self.core_user @@ -126,15 +141,15 @@ def test_create_workflowlevel2_superuser(self): response = view(request) self.assertEqual(response.status_code, 201) - self.assertEqual(response.data['workflowlevel2_pk'], str(data['workflowlevel2_pk'])) + self.assertEqual( + response.data['workflowlevel2_pk'], str(data['workflowlevel2_pk']) + ) def test_create_workflowlevel2sort_normal_user(self): request = self.factory.post('/workflowlevel2sort/') - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) - data = {'workflowlevel2_pk': uuid.uuid4(), - 'workflowlevel1': wflvl1.pk} + data = {'workflowlevel2_pk': uuid.uuid4(), 'workflowlevel1': wflvl1.pk} request = self.factory.post('/workflowlevel2/', data) request.user = self.core_user @@ -142,7 +157,9 @@ def test_create_workflowlevel2sort_normal_user(self): response = view(request) self.assertEqual(response.status_code, 201) - self.assertEqual(response.data['workflowlevel2_pk'], str(data['workflowlevel2_pk'])) + self.assertEqual( + response.data['workflowlevel2_pk'], str(data['workflowlevel2_pk']) + ) class WorkflowLevel2SortUpdateViewsTest(TestCase): @@ -152,9 +169,12 @@ def setUp(self): factories.Group() def test_update_unexisting_workflowlevel2sort(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) data = {'workflowlevel2_pk': 1} @@ -172,11 +192,9 @@ def test_update_workflowlevel2sort_superuser(self): request = self.factory.post('/workflowlevel2sort/') wflvl1 = factories.WorkflowLevel1() - workflowlevel2sort = \ - factories.WorkflowLevel2Sort(workflowlevel1=wflvl1) + workflowlevel2sort = factories.WorkflowLevel2Sort(workflowlevel1=wflvl1) - data = {'workflowlevel2_pk': uuid.uuid4(), - 'workflowlevel1': wflvl1.pk} + data = {'workflowlevel2_pk': uuid.uuid4(), 'workflowlevel1': wflvl1.pk} request = self.factory.post('/workflowlevel2sort/', data) request.user = self.core_user @@ -185,19 +203,17 @@ def test_update_workflowlevel2sort_superuser(self): response = view(request, pk=workflowlevel2sort.pk) self.assertEqual(response.status_code, 200) - workflowlevel2sort = WorkflowLevel2Sort.objects.get( - pk=response.data['id']) - self.assertEqual(str(workflowlevel2sort.workflowlevel2_pk), str(data['workflowlevel2_pk'])) + workflowlevel2sort = WorkflowLevel2Sort.objects.get(pk=response.data['id']) + self.assertEqual( + str(workflowlevel2sort.workflowlevel2_pk), str(data['workflowlevel2_pk']) + ) def test_update_workflowlevel2sort_normal_user(self): request = self.factory.post('/workflowlevel2sort/') - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) - workflowlevel2sort = factories.WorkflowLevel2Sort( - workflowlevel1=wflvl1) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) + workflowlevel2sort = factories.WorkflowLevel2Sort(workflowlevel1=wflvl1) - data = {'workflowlevel2_pk': uuid.uuid4(), - 'workflowlevel1': wflvl1.pk} + data = {'workflowlevel2_pk': uuid.uuid4(), 'workflowlevel1': wflvl1.pk} request = self.factory.post('/workflowlevel2sort/', data) request.user = self.core_user @@ -206,20 +222,18 @@ def test_update_workflowlevel2sort_normal_user(self): self.assertEqual(response.status_code, 200) - workflowlevel2sort = WorkflowLevel2Sort.objects.get( - pk=response.data['id']) - self.assertEqual(workflowlevel2sort.workflowlevel2_pk, - data['workflowlevel2_pk']) + workflowlevel2sort = WorkflowLevel2Sort.objects.get(pk=response.data['id']) + self.assertEqual( + workflowlevel2sort.workflowlevel2_pk, data['workflowlevel2_pk'] + ) def test_update_workflowlevel2sort_diff_org_normal_user(self): request = self.factory.post('/workflowlevel2sort/') another_org = factories.Organization(name='Another Org') wflvl1 = factories.WorkflowLevel1(organization=another_org) - workflowlevel2sort = factories.WorkflowLevel2Sort( - workflowlevel1=wflvl1) + workflowlevel2sort = factories.WorkflowLevel2Sort(workflowlevel1=wflvl1) - data = {'workflowlevel2_pk': uuid.uuid4(), - 'workflowlevel1': wflvl1.pk} + data = {'workflowlevel2_pk': uuid.uuid4(), 'workflowlevel1': wflvl1.pk} request = self.factory.post('/workflowlevel2sort/', data) request.user = self.core_user @@ -247,13 +261,13 @@ def test_delete_workflowlevel2sort_superuser(self): self.assertEqual(response.status_code, 204) self.assertRaises( WorkflowLevel2Sort.DoesNotExist, - WorkflowLevel2Sort.objects.get, pk=workflowlevel2sort.pk) + WorkflowLevel2Sort.objects.get, + pk=workflowlevel2sort.pk, + ) def test_delete_workflowlevel2sort_normal_user(self): - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) - workflowlevel2sort = factories.WorkflowLevel2Sort( - workflowlevel1=wflvl1) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) + workflowlevel2sort = factories.WorkflowLevel2Sort(workflowlevel1=wflvl1) request = self.factory.delete('/workflowlevel2sort/') request.user = self.core_user @@ -262,18 +276,22 @@ def test_delete_workflowlevel2sort_normal_user(self): self.assertEqual(response.status_code, 204) self.assertRaises( WorkflowLevel2Sort.DoesNotExist, - WorkflowLevel2Sort.objects.get, pk=workflowlevel2sort.pk) + WorkflowLevel2Sort.objects.get, + pk=workflowlevel2sort.pk, + ) def test_delete_workflowlevel2sort_diff_org_normal_user(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) another_org = factories.Organization(name='Another Org') wflvl1 = factories.WorkflowLevel1(organization=another_org) - workflowlevel2sort = factories.WorkflowLevel2Sort( - workflowlevel1=wflvl1) + workflowlevel2sort = factories.WorkflowLevel2Sort(workflowlevel1=wflvl1) request = self.factory.delete('/workflowlevel2sort/') request.user = self.core_user diff --git a/workflow/tests/test_workflowlevel2view.py b/workflow/tests/test_workflowlevel2view.py index 1f4136ee..85adbb90 100644 --- a/workflow/tests/test_workflowlevel2view.py +++ b/workflow/tests/test_workflowlevel2view.py @@ -6,8 +6,12 @@ import factories from rest_framework.test import APIRequestFactory from rest_framework.reverse import reverse -from core.models import PERMISSIONS_WORKFLOW_ADMIN, PERMISSIONS_ORG_ADMIN, PERMISSIONS_VIEW_ONLY, \ - PERMISSIONS_WORKFLOW_TEAM +from core.models import ( + PERMISSIONS_WORKFLOW_ADMIN, + PERMISSIONS_ORG_ADMIN, + PERMISSIONS_VIEW_ONLY, + PERMISSIONS_WORKFLOW_TEAM, +) from workflow.models import WorkflowLevel2 from ..views import WorkflowLevel2ViewSet @@ -16,7 +20,9 @@ class WorkflowLevel2ListViewsTest(TestCase): def setUp(self): self.not_default_org = factories.Organization.create(name='Some Org') - wfl1_not_default_org = factories.WorkflowLevel1.create(organization=self.not_default_org) + wfl1_not_default_org = factories.WorkflowLevel1.create( + organization=self.not_default_org + ) factories.WorkflowLevel2.create_batch(2, workflowlevel1=wfl1_not_default_org) self.factory = APIRequestFactory() self.core_user = factories.CoreUser() @@ -31,9 +37,12 @@ def test_list_workflowlevel2_superuser(self): def test_list_workflowlevel2_org_admin(self): request = self.factory.get(reverse('workflowlevel2-list')) - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) wflvl1 = factories.WorkflowLevel1(organization=self.not_default_org) @@ -55,9 +64,11 @@ def test_list_workflowlevel2_program_admin(self): request = self.factory.get(reverse('workflowlevel2-list')) wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) - group_wf_admin = factories.CoreGroup(name='WF Admin', - permissions=PERMISSIONS_WORKFLOW_ADMIN, - organization=self.core_user.organization) + group_wf_admin = factories.CoreGroup( + name='WF Admin', + permissions=PERMISSIONS_WORKFLOW_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_admin) wflvl1.core_groups.add(group_wf_admin) @@ -76,9 +87,11 @@ def test_list_workflowlevel2_program_team(self): request = self.factory.get(reverse('workflowlevel2-list')) wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) - group_wf_team = factories.CoreGroup(name='WF Team', - permissions=PERMISSIONS_WORKFLOW_TEAM, - organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF Team', + permissions=PERMISSIONS_WORKFLOW_TEAM, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1.core_groups.add(group_wf_team) @@ -97,9 +110,11 @@ def test_list_workflowlevel2_view_only(self): request = self.factory.get('/api/workflowlevel2/') wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) - group_wf_team = factories.CoreGroup(name='WF View Only', - permissions=PERMISSIONS_VIEW_ONLY, - organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF View Only', + permissions=PERMISSIONS_VIEW_ONLY, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1.core_groups.add(group_wf_team) @@ -114,7 +129,10 @@ def test_list_workflowlevel2_view_only(self): self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data['results']), 1) - @patch('workflow.pagination.DefaultLimitOffsetPagination.default_limit', new_callable=PropertyMock) + @patch( + 'workflow.pagination.DefaultLimitOffsetPagination.default_limit', + new_callable=PropertyMock, + ) def test_list_workflowlevel2_pagination(self, default_limit_mock): """ For default_limit 1 and pagination by default, list wfl2 endpoint should return 1 wfl2 for each page""" @@ -124,9 +142,12 @@ def test_list_workflowlevel2_pagination(self, default_limit_mock): wfl2_1 = factories.WorkflowLevel2(name='1. wfl2', workflowlevel1=wfl1_1) wfl2_2 = factories.WorkflowLevel2(name='2. wfl2', workflowlevel1=wfl1_1) - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) request = self.factory.get('') @@ -161,8 +182,7 @@ def test_create_workflowlevel2_superuser(self): request = self.factory.post(reverse('workflowlevel2-list')) wflvl1 = factories.WorkflowLevel1() - data = {'name': 'Help Syrians', - 'workflowlevel1': wflvl1.pk} + data = {'name': 'Help Syrians', 'workflowlevel1': wflvl1.pk} request = self.factory.post(reverse('workflowlevel2-list'), data) request.user = self.core_user @@ -173,18 +193,22 @@ def test_create_workflowlevel2_superuser(self): self.assertEqual(response.data['name'], u'Help Syrians') def test_create_workflowlevel2_org_admin(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) request = self.factory.post(reverse('workflowlevel2-list')) wfltype = factories.WorkflowLevelType() - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) - data = {'name': 'Help Syrians', - 'workflowlevel1': wflvl1.pk, - 'type': wfltype.uuid, } + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) + data = { + 'name': 'Help Syrians', + 'workflowlevel1': wflvl1.pk, + 'type': wfltype.uuid, + } request = self.factory.post(reverse('workflowlevel2-list'), data) request.user = self.core_user view = WorkflowLevel2ViewSet.as_view({'post': 'create'}) @@ -198,15 +222,15 @@ def test_create_workflowlevel2_program_admin(self): request = self.factory.post(reverse('workflowlevel2-list')) wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) - group_wf_admin = factories.CoreGroup(name='WF Admin', - permissions=PERMISSIONS_WORKFLOW_ADMIN, - organization=self.core_user.organization) + group_wf_admin = factories.CoreGroup( + name='WF Admin', + permissions=PERMISSIONS_WORKFLOW_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_admin) wflvl1.core_groups.add(group_wf_admin) - data = {'name': 'Help Syrians', - 'workflowlevel1': wflvl1.pk - } + data = {'name': 'Help Syrians', 'workflowlevel1': wflvl1.pk} request = self.factory.post(reverse('workflowlevel2-list'), data) request.user = self.core_user @@ -220,18 +244,21 @@ def test_create_workflowlevel2_program_admin_json(self): request = self.factory.post(reverse('workflowlevel2-list')) wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) - group_wf_admin = factories.CoreGroup(name='WF Admin', - permissions=PERMISSIONS_WORKFLOW_ADMIN, - organization=self.core_user.organization) + group_wf_admin = factories.CoreGroup( + name='WF Admin', + permissions=PERMISSIONS_WORKFLOW_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_admin) wflvl1.core_groups.add(group_wf_admin) - data = {'name': 'Help Syrians', - 'workflowlevel1': wflvl1.pk} + data = {'name': 'Help Syrians', 'workflowlevel1': wflvl1.pk} - request = self.factory.post(reverse('workflowlevel2-list'), - json.dumps(data), - content_type='application/json') + request = self.factory.post( + reverse('workflowlevel2-list'), + json.dumps(data), + content_type='application/json', + ) request.user = self.core_user view = WorkflowLevel2ViewSet.as_view({'post': 'create'}) response = view(request) @@ -241,16 +268,16 @@ def test_create_workflowlevel2_program_admin_json(self): def test_create_workflowlevel2_program_team(self): request = self.factory.post(reverse('workflowlevel2-list')) - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) - group_wf_team = factories.CoreGroup(name='WF Team', - permissions=PERMISSIONS_WORKFLOW_TEAM, - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF Team', + permissions=PERMISSIONS_WORKFLOW_TEAM, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1.core_groups.add(group_wf_team) - data = {'name': 'Help Syrians', - 'workflowlevel1': wflvl1.pk} + data = {'name': 'Help Syrians', 'workflowlevel1': wflvl1.pk} request = self.factory.post(reverse('workflowlevel2-list'), data) request.user = self.core_user @@ -262,17 +289,17 @@ def test_create_workflowlevel2_program_team(self): def test_create_workflowlevel2_view_only(self): request = self.factory.post(reverse('workflowlevel2-list')) - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) - group_wf_team = factories.CoreGroup(name='WF View Only', - permissions=PERMISSIONS_VIEW_ONLY, - organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF View Only', + permissions=PERMISSIONS_VIEW_ONLY, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1.core_groups.add(group_wf_team) - data = {'name': 'Help Syrians', - 'workflowlevel1': wflvl1.pk} + data = {'name': 'Help Syrians', 'workflowlevel1': wflvl1.pk} request = self.factory.post(reverse('workflowlevel2-list'), data) request.user = self.core_user @@ -283,15 +310,15 @@ def test_create_workflowlevel2_view_only(self): def test_create_workflowlevel2_uuid_is_self_generated(self): wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) - group_wf_team = factories.CoreGroup(name='WF Team', - permissions=PERMISSIONS_WORKFLOW_TEAM, - organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF Team', + permissions=PERMISSIONS_WORKFLOW_TEAM, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1.core_groups.add(group_wf_team) - data = { - 'name': 'Save the Children', - 'workflowlevel1': wflvl1.pk} + data = {'name': 'Save the Children', 'workflowlevel1': wflvl1.pk} request = self.factory.post(reverse('workflowlevel2-list'), data) request.user = self.core_user @@ -309,16 +336,17 @@ def setUp(self): factories.Group() def test_update_unexisting_workflowlevel2(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) data = {'name': 'Community awareness program conducted to plant trees'} - request = self.factory.put( - reverse('workflowlevel2-detail', args=(228,)), data - ) + request = self.factory.put(reverse('workflowlevel2-detail', args=(228,)), data) request.user = self.core_user view = WorkflowLevel2ViewSet.as_view({'put': 'update'}) response = view(request, pk=288) @@ -333,8 +361,10 @@ def test_update_workflowlevel2_superuser(self): wflvl1 = factories.WorkflowLevel1() workflowlevel2 = factories.WorkflowLevel2(workflowlevel1=wflvl1) - data = {'name': 'Community awareness program conducted to plant trees', - 'workflowlevel1': wflvl1.pk} + data = { + 'name': 'Community awareness program conducted to plant trees', + 'workflowlevel1': wflvl1.pk, + } request = self.factory.put( reverse('workflowlevel2-detail', args=(str(workflowlevel2.pk),)), data @@ -348,20 +378,24 @@ def test_update_workflowlevel2_superuser(self): self.assertEqual(workflowlevel2.name, data['name']) def test_update_workflowlevel2_org_admin(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) request = self.factory.post(reverse('workflowlevel2-list')) - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) workflowlevel2 = factories.WorkflowLevel2(workflowlevel1=wflvl1) wfltype = factories.WorkflowLevelType() - data = {'name': 'Community awareness program conducted to plant trees', - 'workflowlevel1': wflvl1.pk, - 'type': wfltype.uuid} + data = { + 'name': 'Community awareness program conducted to plant trees', + 'workflowlevel1': wflvl1.pk, + 'type': wfltype.uuid, + } request = self.factory.put( reverse('workflowlevel2-detail', args=(str(workflowlevel2.pk),)), data @@ -376,9 +410,12 @@ def test_update_workflowlevel2_org_admin(self): self.assertEqual(workflowlevel2.type, wfltype) def test_update_workflowlevel2_diff_org_admin(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) request = self.factory.post(reverse('workflowlevel2-list')) @@ -386,8 +423,10 @@ def test_update_workflowlevel2_diff_org_admin(self): wflvl1 = factories.WorkflowLevel1(organization=another_org) workflowlevel2 = factories.WorkflowLevel2(workflowlevel1=wflvl1) - data = {'name': 'Community awareness program conducted to plant trees', - 'workflowlevel1': wflvl1.pk} + data = { + 'name': 'Community awareness program conducted to plant trees', + 'workflowlevel1': wflvl1.pk, + } request = self.factory.put( reverse('workflowlevel2-detail', args=(str(workflowlevel2.pk),)), data @@ -399,18 +438,21 @@ def test_update_workflowlevel2_diff_org_admin(self): def test_update_workflowlevel2_program_admin(self): request = self.factory.post(reverse('workflowlevel2-list')) - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) workflowlevel2 = factories.WorkflowLevel2(workflowlevel1=wflvl1) - group_wf_admin = factories.CoreGroup(name='WF Admin', - permissions=PERMISSIONS_WORKFLOW_ADMIN, - organization=self.core_user.organization) + group_wf_admin = factories.CoreGroup( + name='WF Admin', + permissions=PERMISSIONS_WORKFLOW_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_admin) wflvl1.core_groups.add(group_wf_admin) - data = {'name': 'Community awareness program conducted to plant trees', - 'workflowlevel1': wflvl1.pk} + data = { + 'name': 'Community awareness program conducted to plant trees', + 'workflowlevel1': wflvl1.pk, + } request = self.factory.put( reverse('workflowlevel2-detail', args=(str(workflowlevel2.pk),)), data @@ -425,23 +467,26 @@ def test_update_workflowlevel2_program_admin(self): def test_update_workflowlevel2_program_admin_json(self): request = self.factory.post(reverse('workflowlevel2-list')) - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) workflowlevel2 = factories.WorkflowLevel2(workflowlevel1=wflvl1) - group_wf_admin = factories.CoreGroup(name='WF Admin', - permissions=PERMISSIONS_WORKFLOW_ADMIN, - organization=self.core_user.organization) + group_wf_admin = factories.CoreGroup( + name='WF Admin', + permissions=PERMISSIONS_WORKFLOW_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_admin) wflvl1.core_groups.add(group_wf_admin) - data = {'name': 'Community awareness program conducted to plant trees', - 'workflowlevel1': wflvl1.pk} + data = { + 'name': 'Community awareness program conducted to plant trees', + 'workflowlevel1': wflvl1.pk, + } request = self.factory.put( reverse('workflowlevel2-detail', args=(str(workflowlevel2.pk),)), json.dumps(data), - content_type='application/json' + content_type='application/json', ) request.user = self.core_user view = WorkflowLevel2ViewSet.as_view({'put': 'update'}) @@ -453,18 +498,21 @@ def test_update_workflowlevel2_program_admin_json(self): def test_update_workflowlevel2_program_team(self): request = self.factory.post(reverse('workflowlevel2-list')) - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) workflowlevel2 = factories.WorkflowLevel2(workflowlevel1=wflvl1) - group_wf_team = factories.CoreGroup(name='WF Team', - permissions=PERMISSIONS_WORKFLOW_TEAM, - organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF Team', + permissions=PERMISSIONS_WORKFLOW_TEAM, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1.core_groups.add(group_wf_team) - data = {'name': 'Community awareness program conducted to plant trees', - 'workflowlevel1': wflvl1.pk} + data = { + 'name': 'Community awareness program conducted to plant trees', + 'workflowlevel1': wflvl1.pk, + } request = self.factory.put( reverse('workflowlevel2-detail', args=(str(workflowlevel2.pk),)), data @@ -479,18 +527,21 @@ def test_update_workflowlevel2_program_team(self): def test_update_workflowlevel2_view_only(self): request = self.factory.post(reverse('workflowlevel2-list')) - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) workflowlevel2 = factories.WorkflowLevel2(workflowlevel1=wflvl1) - group_wf_team = factories.CoreGroup(name='WF View Only', - permissions=PERMISSIONS_VIEW_ONLY, - organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF View Only', + permissions=PERMISSIONS_VIEW_ONLY, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1.core_groups.add(group_wf_team) - data = {'name': 'Community awareness program conducted to plant trees', - 'workflowlevel1': wflvl1.pk} + data = { + 'name': 'Community awareness program conducted to plant trees', + 'workflowlevel1': wflvl1.pk, + } request = self.factory.put( reverse('workflowlevel2-detail', args=(str(workflowlevel2.pk),)), data @@ -504,28 +555,31 @@ def test_update_workflowlevel2_view_only(self): def test_update_workflowlevel2_uuid_is_self_generated(self): wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) - group_wf_team = factories.CoreGroup(name='WF Team', - permissions=PERMISSIONS_WORKFLOW_TEAM, - organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF Team', + permissions=PERMISSIONS_WORKFLOW_TEAM, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1.core_groups.add(group_wf_team) - data = {'name': 'Community awareness program conducted to plant trees', - 'workflowlevel1': wflvl1.pk} + data = { + 'name': 'Community awareness program conducted to plant trees', + 'workflowlevel1': wflvl1.pk, + } request = self.factory.post(reverse('workflowlevel2-list'), data) request.user = self.core_user view = WorkflowLevel2ViewSet.as_view({'post': 'create'}) response = view(request) self.assertEqual(response.status_code, 201) first_level2_uuid = response.data['level2_uuid'] - data = {'name': 'Community awareness program conducted to plant trees', - 'workflowlevel1': wflvl1.pk, - 'level2_uuid': '84a9888-4149-11e8-842f-0ed5f89f718b' - } + data = { + 'name': 'Community awareness program conducted to plant trees', + 'workflowlevel1': wflvl1.pk, + 'level2_uuid': '84a9888-4149-11e8-842f-0ed5f89f718b', + } pk = first_level2_uuid - request = self.factory.put( - reverse('workflowlevel2-detail', args=(pk,)), data - ) + request = self.factory.put(reverse('workflowlevel2-detail', args=(pk,)), data) request.user = self.core_user view = WorkflowLevel2ViewSet.as_view({'put': 'update'}) response = view(request, pk=pk) @@ -551,16 +605,20 @@ def test_delete_workflowlevel2_superuser(self): self.assertEqual(response.status_code, 204) self.assertRaises( WorkflowLevel2.DoesNotExist, - WorkflowLevel2.objects.get, pk=str(workflowlevel2.pk)) + WorkflowLevel2.objects.get, + pk=str(workflowlevel2.pk), + ) def test_delete_workflowlevel2_org_admin(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) workflowlevel2 = factories.WorkflowLevel2(workflowlevel1=wflvl1) request = self.factory.delete(reverse('workflowlevel2-list')) @@ -570,12 +628,17 @@ def test_delete_workflowlevel2_org_admin(self): self.assertEqual(response.status_code, 204) self.assertRaises( WorkflowLevel2.DoesNotExist, - WorkflowLevel2.objects.get, pk=str(workflowlevel2.pk)) + WorkflowLevel2.objects.get, + pk=str(workflowlevel2.pk), + ) def test_delete_workflowlevel2_diff_org_admin(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) another_org = factories.Organization(name='Another Org') @@ -592,9 +655,11 @@ def test_delete_workflowlevel2_diff_org_admin(self): def test_delete_workflowlevel2_program_admin(self): wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) workflowlevel2 = factories.WorkflowLevel2(workflowlevel1=wflvl1) - group_wf_admin = factories.CoreGroup(name='WF Admin', - permissions=PERMISSIONS_WORKFLOW_ADMIN, - organization=self.core_user.organization) + group_wf_admin = factories.CoreGroup( + name='WF Admin', + permissions=PERMISSIONS_WORKFLOW_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_admin) wflvl1.core_groups.add(group_wf_admin) @@ -607,15 +672,19 @@ def test_delete_workflowlevel2_program_admin(self): self.assertEqual(response.status_code, 204) self.assertRaises( WorkflowLevel2.DoesNotExist, - WorkflowLevel2.objects.get, pk=str(workflowlevel2.pk)) + WorkflowLevel2.objects.get, + pk=str(workflowlevel2.pk), + ) def test_delete_workflowlevel2_diff_org(self): another_org = factories.Organization(name='Another Org') wflvl1 = factories.WorkflowLevel1(organization=another_org) workflowlevel2 = factories.WorkflowLevel2(workflowlevel1=wflvl1) - group_wf_admin = factories.CoreGroup(name='WF Admin', - permissions=PERMISSIONS_WORKFLOW_ADMIN, - organization=self.core_user.organization) + group_wf_admin = factories.CoreGroup( + name='WF Admin', + permissions=PERMISSIONS_WORKFLOW_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_admin) wflvl1.core_groups.add(group_wf_admin) @@ -627,13 +696,14 @@ def test_delete_workflowlevel2_diff_org(self): WorkflowLevel2.objects.get(pk=str(workflowlevel2.pk)) def test_delete_workflowlevel2_program_team(self): - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) workflowlevel2 = factories.WorkflowLevel2(workflowlevel1=wflvl1) - group_wf_team = factories.CoreGroup(name='WF Team', - permissions=PERMISSIONS_WORKFLOW_TEAM, - organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF Team', + permissions=PERMISSIONS_WORKFLOW_TEAM, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1.core_groups.add(group_wf_team) @@ -645,13 +715,14 @@ def test_delete_workflowlevel2_program_team(self): WorkflowLevel2.objects.get(pk=str(workflowlevel2.pk)) def test_delete_workflowlevel2_view_only(self): - wflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) workflowlevel2 = factories.WorkflowLevel2(workflowlevel1=wflvl1) - group_wf_team = factories.CoreGroup(name='WF View Only', - permissions=PERMISSIONS_VIEW_ONLY, - organization=self.core_user.organization) + group_wf_team = factories.CoreGroup( + name='WF View Only', + permissions=PERMISSIONS_VIEW_ONLY, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_wf_team) wflvl1.core_groups.add(group_wf_team) @@ -679,22 +750,25 @@ def setUp(self): self.core_user = factories.CoreUser() def test_filter_workflowlevel2_wkflvl1_name_org_admin(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) wkflvl1_1 = factories.WorkflowLevel1(organization=self.core_user.organization) wkflvl1_2 = factories.WorkflowLevel1( - name='Construction Project', - organization=self.core_user.organization) + name='Construction Project', organization=self.core_user.organization + ) wkflvl2 = factories.WorkflowLevel2(workflowlevel1=wkflvl1_1) - factories.WorkflowLevel2( - name='Develop brief survey', workflowlevel1=wkflvl1_2) + factories.WorkflowLevel2(name='Develop brief survey', workflowlevel1=wkflvl1_2) request = self.factory.get( - '{}?workflowlevel1__name={}'.format(reverse('workflowlevel2-list'), - wkflvl1_1.name) + '{}?workflowlevel1__name={}'.format( + reverse('workflowlevel2-list'), wkflvl1_1.name + ) ) request.user = self.core_user view = WorkflowLevel2ViewSet.as_view({'get': 'list'}) @@ -704,22 +778,23 @@ def test_filter_workflowlevel2_wkflvl1_name_org_admin(self): self.assertEqual(response.data['results'][0]['name'], wkflvl2.name) def test_filter_workflowlevel2_wkflvl1_id_org_admin(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) - wkflvl1_1 = factories.WorkflowLevel1( - organization=self.core_user.organization) - wkflvl1_2 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wkflvl1_1 = factories.WorkflowLevel1(organization=self.core_user.organization) + wkflvl1_2 = factories.WorkflowLevel1(organization=self.core_user.organization) wkflvl2 = factories.WorkflowLevel2(workflowlevel1=wkflvl1_1) - factories.WorkflowLevel2( - name='Develop brief survey', workflowlevel1=wkflvl1_2) + factories.WorkflowLevel2(name='Develop brief survey', workflowlevel1=wkflvl1_2) request = self.factory.get( - '{}?workflowlevel1__id={}'.format(reverse('workflowlevel2-list'), - wkflvl1_1.pk) + '{}?workflowlevel1__id={}'.format( + reverse('workflowlevel2-list'), wkflvl1_1.pk + ) ) request.user = self.core_user view = WorkflowLevel2ViewSet.as_view({'get': 'list'}) @@ -729,25 +804,26 @@ def test_filter_workflowlevel2_wkflvl1_id_org_admin(self): self.assertEqual(response.data['results'][0]['name'], wkflvl2.name) def test_filter_workflowlevel2_create_date_range_org_admin(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) - wkflvl1 = factories.WorkflowLevel1( - organization=self.core_user.organization) + wkflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) date1 = '2019-05-01' date2 = '2019-05-02' date3 = '2019-05-03' - factories.WorkflowLevel2( - workflowlevel1=wkflvl1, create_date=date1) + factories.WorkflowLevel2(workflowlevel1=wkflvl1, create_date=date1) level22_uuid = uuid.uuid4() wkflvl22 = WorkflowLevel2.objects.create( - workflowlevel1=wkflvl1, create_date=date2, level2_uuid=level22_uuid) - factories.WorkflowLevel2( - workflowlevel1=wkflvl1, create_date=date3) + workflowlevel1=wkflvl1, create_date=date2, level2_uuid=level22_uuid + ) + factories.WorkflowLevel2(workflowlevel1=wkflvl1, create_date=date3) request = self.factory.get( f'{reverse("workflowlevel2-list")}?create_date_gte={date2}&create_date_lte={date2}' @@ -761,20 +837,27 @@ def test_filter_workflowlevel2_create_date_range_org_admin(self): self.assertEqual(response.data['results'][0]['level2_uuid'], str(level22_uuid)) def test_filter_workflowlevel2_status_org_admin(self): - group_org_admin = factories.CoreGroup(name='Org Admin', is_org_level=True, - permissions=PERMISSIONS_ORG_ADMIN, - organization=self.core_user.organization) + group_org_admin = factories.CoreGroup( + name='Org Admin', + is_org_level=True, + permissions=PERMISSIONS_ORG_ADMIN, + organization=self.core_user.organization, + ) self.core_user.core_groups.add(group_org_admin) wkflvl1 = factories.WorkflowLevel1(organization=self.core_user.organization) - wfl_status1 = factories.WorkflowLevelStatus(name="Started Test Status", short_name="started") - wfl_status2 = factories.WorkflowLevelStatus(name="Finished Test Status", short_name="finished") - wkflvl2_1 = factories.WorkflowLevel2(name='Started brief survey', - workflowlevel1=wkflvl1, - status=wfl_status1) - wkflvl2_2 = factories.WorkflowLevel2(name='Finished brief survey', - workflowlevel1=wkflvl1, - status=wfl_status2) + wfl_status1 = factories.WorkflowLevelStatus( + name="Started Test Status", short_name="started" + ) + wfl_status2 = factories.WorkflowLevelStatus( + name="Finished Test Status", short_name="finished" + ) + wkflvl2_1 = factories.WorkflowLevel2( + name='Started brief survey', workflowlevel1=wkflvl1, status=wfl_status1 + ) + wkflvl2_2 = factories.WorkflowLevel2( + name='Finished brief survey', workflowlevel1=wkflvl1, status=wfl_status2 + ) # filter by status.uuid request = self.factory.get( diff --git a/workflow/tests/test_workflowlevelstatus.py b/workflow/tests/test_workflowlevelstatus.py index 789b6ae7..ed92ba44 100644 --- a/workflow/tests/test_workflowlevelstatus.py +++ b/workflow/tests/test_workflowlevelstatus.py @@ -29,8 +29,7 @@ def test_create_workflowlevelstatus(request_factory, org_member): @pytest.mark.django_db() def test_update_workflowlevelstatus(request_factory, org_member): wflstatus = factories.WorkflowLevelStatus(name='change this') - data = {"name": "Changed WFL Status Name", - "short_name": "changed"} + data = {"name": "Changed WFL Status Name", "short_name": "changed"} request = request_factory.put('', data) request.user = org_member view = WorkflowLevelStatusViewSet.as_view({'put': 'update'}) diff --git a/workflow/views/workflowlevel1.py b/workflow/views/workflowlevel1.py index bc4966f1..e3f6291d 100644 --- a/workflow/views/workflowlevel1.py +++ b/workflow/views/workflowlevel1.py @@ -34,6 +34,7 @@ class WorkflowLevel1ViewSet(viewsets.ModelViewSet): create: Create a new workflow instance. """ + # Remove CSRF request verification for posts to this API @method_decorator(csrf_exempt) def dispatch(self, *args, **kwargs): @@ -60,8 +61,9 @@ def create(self, request, *args, **kwargs): serializer.is_valid(raise_exception=True) self.perform_create(serializer) # inherited from CreateModelMixin headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_201_CREATED, - headers=headers) + return Response( + serializer.data, status=status.HTTP_201_CREATED, headers=headers + ) def perform_create(self, serializer): organization = self.request.user.organization @@ -76,7 +78,10 @@ def destroy(self, request, *args, **kwargs): ordering_fields = ('name',) ordering = ('name',) filterset_fields = ('name', 'level1_uuid') - filter_backends = (django_filters.rest_framework.DjangoFilterBackend, filters.OrderingFilter) + filter_backends = ( + django_filters.rest_framework.DjangoFilterBackend, + filters.OrderingFilter, + ) queryset = WorkflowLevel1.objects.all() serializer_class = WorkflowLevel1Serializer diff --git a/workflow/views/workflowlevel2.py b/workflow/views/workflowlevel2.py index f95dc22a..8c75de20 100644 --- a/workflow/views/workflowlevel2.py +++ b/workflow/views/workflowlevel2.py @@ -6,7 +6,12 @@ from core.permissions import IsOrgMember from workflow.filters import WorkflowLevel2Filter -from workflow.models import WorkflowLevel2, WorkflowLevel2Sort, WorkflowTeam, ROLE_ORGANIZATION_ADMIN +from workflow.models import ( + WorkflowLevel2, + WorkflowLevel2Sort, + WorkflowTeam, + ROLE_ORGANIZATION_ADMIN, +) from workflow.serializers import WorkflowLevel2Serializer, WorkflowLevel2SortSerializer from workflow.permissions import CoreGroupsPermissions from workflow.pagination import DefaultLimitOffsetPagination @@ -31,6 +36,7 @@ class WorkflowLevel2ViewSet(viewsets.ModelViewSet): create: Create a new workflow level 2 instance. """ + # Remove CSRF request verification for posts to this API @method_decorator(csrf_exempt) def dispatch(self, *args, **kwargs): @@ -65,7 +71,7 @@ def perform_create(self, serializer): ordering = ('name',) filter_backends = ( django_filters.rest_framework.DjangoFilterBackend, - filters.OrderingFilter + filters.OrderingFilter, ) filter_class = WorkflowLevel2Filter queryset = WorkflowLevel2.objects.all() @@ -102,11 +108,12 @@ def list(self, request, *args, **kwargs): if ROLE_ORGANIZATION_ADMIN in user_groups: organization_id = request.user.organization_id queryset = queryset.filter( - workflowlevel1__organization_id=organization_id) + workflowlevel1__organization_id=organization_id + ) else: wflvl1_ids = WorkflowTeam.objects.filter( - workflow_user=request.user).values_list( - 'workflowlevel1__id', flat=True) + workflow_user=request.user + ).values_list('workflowlevel1__id', flat=True) queryset = queryset.filter(workflowlevel1__in=wflvl1_ids) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) diff --git a/workflow/views/workflowlevelstatus.py b/workflow/views/workflowlevelstatus.py index c120397b..8df72bd2 100644 --- a/workflow/views/workflowlevelstatus.py +++ b/workflow/views/workflowlevelstatus.py @@ -43,8 +43,9 @@ class WorkflowLevelStatusViewSet(viewsets.ModelViewSet): Delete the WorkflowlevelStatus instance. """ + queryset = WorkflowLevelStatus.objects.all() - ordering = ('order', ) - filter_backends = (filters.OrderingFilter, ) + ordering = ('order',) + filter_backends = (filters.OrderingFilter,) serializer_class = WorkflowLevelStatusSerializer pagination_class = DefaultLimitOffsetPagination diff --git a/workflow/views/workflowleveltype.py b/workflow/views/workflowleveltype.py index 14c03fe0..1a023674 100644 --- a/workflow/views/workflowleveltype.py +++ b/workflow/views/workflowleveltype.py @@ -43,8 +43,9 @@ class WorkflowLevelTypeViewSet(viewsets.ModelViewSet): Delete the workflowleveltype instance. """ + queryset = WorkflowLevelType.objects.all() - ordering = ('create_date', ) - filter_backends = (filters.OrderingFilter, ) + ordering = ('create_date',) + filter_backends = (filters.OrderingFilter,) serializer_class = WorkflowLevelTypeSerializer pagination_class = DefaultCursorPagination diff --git a/workflow/views/workflowteam.py b/workflow/views/workflowteam.py index 7a9b8568..c5bc773d 100644 --- a/workflow/views/workflowteam.py +++ b/workflow/views/workflowteam.py @@ -28,18 +28,22 @@ class WorkflowTeamViewSet(viewsets.ModelViewSet): create: Create a new workflow team instance. """ + def list(self, request, *args, **kwargs): # Use this queryset or the django-filters lib will not work queryset = self.filter_queryset(self.get_queryset()) if not request.user.is_superuser: if ROLE_ORGANIZATION_ADMIN in request.user.groups.values_list( - 'name', flat=True): + 'name', flat=True + ): organization_id = request.user.organization_id queryset = queryset.filter( - workflow_user__organization_id=organization_id) + workflow_user__organization_id=organization_id + ) else: wflvl1_ids = WorkflowTeam.objects.filter( - workflow_user=request.user).values_list('workflowlevel1__id', flat=True) + workflow_user=request.user + ).values_list('workflowlevel1__id', flat=True) queryset = queryset.filter(workflowlevel1__in=wflvl1_ids) nested = request.GET.get('nested_models')