From 793e3c0028d469076a3737b691ba0820116f3986 Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Thu, 12 Sep 2024 17:23:28 -0500 Subject: [PATCH] infra: format with ruff --- Makefile | 3 + banners/apps.py | 3 +- banners/migrations/0001_initial.py | 17 +- banners/models.py | 17 +- blogs/admin.py | 18 +- blogs/apps.py | 3 +- blogs/factories.py | 10 +- blogs/management/commands/update_blogs.py | 8 +- blogs/migrations/0001_initial.py | 184 ++++--- ...02_remove_translations_and_contributors.py | 27 +- ...0003_alter_relatedblog_creator_and_more.py | 27 +- blogs/parser.py | 30 +- blogs/templatetags/blogs.py | 6 +- blogs/tests/test_models.py | 15 +- blogs/tests/test_parser.py | 17 +- blogs/tests/test_templatetags.py | 49 +- blogs/tests/test_views.py | 11 +- blogs/tests/utils.py | 2 +- blogs/urls.py | 2 +- blogs/views.py | 17 +- boxes/admin.py | 2 +- boxes/apps.py | 3 +- boxes/factories.py | 17 +- boxes/migrations/0001_initial.py | 54 +- boxes/migrations/0002_auto_20150416_1853.py | 19 +- boxes/migrations/0003_auto_20171101_2138.py | 23 +- ..._box_creator_alter_box_last_modified_by.py | 27 +- boxes/models.py | 5 +- boxes/templatetags/boxes.py | 6 +- boxes/tests.py | 12 +- boxes/urls.py | 2 +- boxes/views.py | 1 + cms/admin.py | 25 +- cms/apps.py | 3 +- .../commands/create_initial_data.py | 71 ++- cms/models.py | 4 +- cms/templatetags/cms.py | 10 +- cms/tests.py | 53 +- cms/views.py | 16 +- codesamples/apps.py | 3 +- codesamples/factories.py | 29 +- codesamples/migrations/0001_initial.py | 77 ++- .../migrations/0002_auto_20150416_1853.py | 37 +- .../migrations/0003_auto_20170821_2000.py | 35 +- .../0004_alter_codesample_creator_and_more.py | 27 +- codesamples/models.py | 6 +- codesamples/tests.py | 14 +- community/admin.py | 10 +- community/apps.py | 3 +- community/managers.py | 9 +- community/migrations/0001_initial.py | 232 +++++--- .../0001_squashed_0004_auto_20170831_0541.py | 271 +++++++--- .../migrations/0002_auto_20150416_1853.py | 19 +- .../migrations/0003_auto_20170831_0358.py | 7 +- .../migrations/0004_auto_20170831_0541.py | 7 +- ...or_alter_link_last_modified_by_and_more.py | 139 +++-- community/models.py | 75 +-- community/templatetags/community.py | 8 +- community/tests/test_managers.py | 10 +- community/tests/test_models.py | 7 +- community/tests/test_views.py | 10 +- community/urls.py | 6 +- companies/admin.py | 6 +- companies/apps.py | 3 +- companies/factories.py | 13 +- companies/migrations/0001_initial.py | 46 +- .../migrations/0002_auto_20150416_1853.py | 20 +- .../migrations/0003_auto_20170814_0301.py | 7 +- .../migrations/0004_auto_20170821_2000.py | 19 +- .../migrations/0005_auto_20180705_0352.py | 7 +- companies/models.py | 12 +- companies/templatetags/companies.py | 12 +- companies/tests.py | 9 +- custom_storages/storages.py | 16 +- dev-requirements.txt | 2 + docs/source/conf.py | 64 +-- downloads/admin.py | 12 +- downloads/api.py | 116 ++-- downloads/apps.py | 3 +- downloads/factories.py | 64 +-- downloads/managers.py | 16 +- downloads/migrations/0001_initial.py | 230 ++++++-- .../migrations/0002_auto_20150416_1853.py | 19 +- .../migrations/0003_auto_20150824_1612.py | 11 +- .../migrations/0004_auto_20170821_2000.py | 9 +- .../0005_move_release_page_content.py | 19 +- .../migrations/0006_auto_20180705_0352.py | 15 +- .../migrations/0007_auto_20220809_1655.py | 17 +- .../migrations/0008_auto_20220907_2102.py | 11 +- .../0009_releasefile_sigstore_bundle_file.py | 9 +- .../0010_releasefile_sbom_spdx2_file.py | 9 +- ...ator_alter_os_last_modified_by_and_more.py | 75 ++- downloads/models.py | 205 ++++--- downloads/search_indexes.py | 16 +- downloads/serializers.py | 59 +- downloads/templatetags/download_tags.py | 23 +- downloads/tests/base.py | 58 +- downloads/tests/test_models.py | 26 +- downloads/tests/test_views.py | 349 ++++++------ downloads/urls.py | 14 +- downloads/views.py | 102 ++-- events/admin.py | 10 +- events/apps.py | 3 +- events/factories.py | 47 +- events/forms.py | 41 +- events/importer.py | 34 +- events/migrations/0001_initial.py | 244 ++++++--- events/migrations/0002_auto_20150321_1247.py | 11 +- events/migrations/0003_auto_20150416_1853.py | 19 +- events/migrations/0004_auto_20170814_0519.py | 13 +- events/migrations/0005_auto_20170821_2000.py | 9 +- ...006_change_end_date_for_occurring_rules.py | 15 +- events/migrations/0007_auto_20180705_0352.py | 7 +- ...r_alter_alarm_last_modified_by_and_more.py | 75 ++- events/models.py | 70 +-- events/search_indexes.py | 20 +- events/templatetags/events.py | 3 +- events/tests/test_forms.py | 44 +- events/tests/test_importer.py | 56 +- events/tests/test_models.py | 14 +- events/tests/test_utils.py | 134 +++-- events/tests/test_views.py | 138 +++-- events/urls.py | 32 +- events/utils.py | 68 +-- events/views.py | 80 +-- fastly/utils.py | 8 +- jobs/admin.py | 28 +- jobs/apps.py | 5 +- jobs/factories.py | 92 ++-- jobs/feeds.py | 19 +- jobs/forms.py | 48 +- jobs/listeners.py | 77 ++- jobs/management/commands/expire_jobs.py | 10 +- .../commands/jobs_monthly_report.py | 8 +- jobs/managers.py | 25 +- jobs/migrations/0001_initial.py | 208 ++++--- jobs/migrations/0002_auto_20150211_1634.py | 43 +- jobs/migrations/0003_auto_20150211_1738.py | 9 +- jobs/migrations/0004_auto_20150216_1544.py | 11 +- jobs/migrations/0005_job_other_job_type.py | 9 +- jobs/migrations/0006_region_nullable.py | 7 +- jobs/migrations/0007_auto_20150227_2223.py | 17 +- jobs/migrations/0008_auto_20150316_1205.py | 17 +- jobs/migrations/0009_auto_20150317_1815.py | 45 +- jobs/migrations/0010_auto_20150416_1853.py | 52 +- jobs/migrations/0011_jobreviewcomment.py | 56 +- jobs/migrations/0012_auto_20170809_1849.py | 29 +- jobs/migrations/0013_auto_20170810_1625.py | 7 +- jobs/migrations/0013_auto_20170810_1627.py | 7 +- jobs/migrations/0014_merge.py | 8 +- jobs/migrations/0015_auto_20170814_0301.py | 9 +- jobs/migrations/0016_auto_20170821_2000.py | 19 +- jobs/migrations/0017_auto_20180705_0348.py | 26 +- jobs/migrations/0018_auto_20180705_0352.py | 11 +- jobs/migrations/0019_job_submitted_by.py | 11 +- jobs/migrations/0020_auto_20191101_1601.py | 9 +- ...tor_alter_job_last_modified_by_and_more.py | 51 +- jobs/models.py | 181 +++---- jobs/search_indexes.py | 26 +- jobs/tests/test_models.py | 19 +- jobs/tests/test_views.py | 508 ++++++++---------- jobs/urls.py | 40 +- jobs/views.py | 192 ++++--- mailing/admin.py | 19 +- mailing/apps.py | 2 +- mailing/forms.py | 1 - mailing/tests/forms.py | 1 + mailing/tests/models.py | 3 +- mailing/tests/test_forms.py | 2 +- manage.py | 3 +- membership/apps.py | 3 +- membership/tests/test_views.py | 9 +- membership/urls.py | 2 +- membership/views.py | 7 +- minutes/admin.py | 6 +- minutes/apps.py | 3 +- minutes/feeds.py | 8 +- .../management/commands/move_meeting_notes.py | 8 +- minutes/managers.py | 2 +- minutes/migrations/0001_initial.py | 58 +- minutes/migrations/0002_auto_20150416_1853.py | 19 +- ..._creator_alter_minutes_last_modified_by.py | 27 +- minutes/models.py | 21 +- minutes/tests/test_models.py | 23 +- minutes/tests/test_views.py | 82 +-- minutes/urls.py | 10 +- minutes/views.py | 20 +- nominations/admin.py | 4 +- nominations/apps.py | 3 +- nominations/forms.py | 8 +- nominations/migrations/0001_initial.py | 5 +- .../migrations/0002_auto_20190514_1435.py | 28 +- nominations/models.py | 63 +-- nominations/urls.py | 28 +- nominations/views.py | 14 +- pages/admin.py | 34 +- pages/api.py | 37 +- pages/apps.py | 3 +- pages/factories.py | 9 +- .../commands/fix_success_story_images.py | 16 +- .../commands/import_pages_from_svn.py | 63 ++- pages/middleware.py | 12 +- pages/migrations/0001_initial.py | 119 ++-- pages/migrations/0002_auto_20150416_1853.py | 19 +- pages/migrations/0003_auto_20230214_2113.py | 20 +- ...age_creator_alter_page_last_modified_by.py | 27 +- pages/models.py | 50 +- pages/parser.py | 26 +- pages/search_indexes.py | 10 +- pages/serializers.py | 17 +- pages/tests/base.py | 6 +- pages/tests/test_api.py | 42 +- pages/tests/test_models.py | 25 +- pages/tests/test_parser.py | 16 +- pages/tests/test_views.py | 20 +- pages/urls.py | 2 +- pages/views.py | 22 +- peps/apps.py | 3 +- peps/converters.py | 135 +++-- peps/management/commands/dump_pep_pages.py | 5 +- .../management/commands/generate_pep_pages.py | 37 +- peps/templatetags/peps.py | 6 +- peps/tests/__init__.py | 4 +- peps/tests/test_commands.py | 27 +- peps/tests/test_converters.py | 48 +- pydotorg/celery.py | 2 + pydotorg/compilers.py | 1 - pydotorg/context_processors.py | 27 +- pydotorg/drf.py | 36 +- pydotorg/middleware.py | 4 +- pydotorg/mixins.py | 11 +- pydotorg/resources.py | 6 +- pydotorg/settings/base.py | 333 ++++++------ pydotorg/settings/cabotage.py | 72 ++- pydotorg/settings/local.py | 47 +- pydotorg/settings/pipeline.py | 94 ++-- pydotorg/settings/static.py | 16 +- pydotorg/tests/test_context_processors.py | 62 +-- pydotorg/tests/test_middleware.py | 15 +- pydotorg/tests/test_resources.py | 9 +- pydotorg/tests/test_views.py | 24 +- pydotorg/urls.py | 87 ++- pydotorg/urls_api.py | 14 +- pydotorg/views.py | 38 +- pydotorg/wsgi.py | 2 + ruff.toml | 24 + sponsors/admin.py | 268 ++++----- sponsors/api.py | 21 +- sponsors/apps.py | 3 +- sponsors/contracts.py | 20 +- sponsors/forms.py | 101 ++-- .../check_sponsorship_assets_due_date.py | 19 +- .../management/commands/create_contracts.py | 2 + .../create_pycon_vouchers_for_sponsors.py | 14 +- .../commands/fullfill_pycon_2022.py | 18 +- sponsors/migrations/0001_initial.py | 5 +- .../migrations/0002_auto_20150416_1853.py | 1 - .../migrations/0003_auto_20170821_2000.py | 1 - .../migrations/0004_auto_20201014_1622.py | 17 +- .../migrations/0005_auto_20201015_0908.py | 5 +- .../migrations/0006_auto_20201016_1517.py | 1 - .../migrations/0007_auto_20201021_1410.py | 1 - .../migrations/0008_auto_20201028_1814.py | 5 +- .../migrations/0009_auto_20201103_1259.py | 1 - .../migrations/0010_auto_20201103_1313.py | 9 +- .../migrations/0011_auto_20201111_1724.py | 1 - .../0012_sponsorship_for_modified_package.py | 1 - ...3_sponsorbenefit_benefit_internal_value.py | 1 - .../migrations/0014_auto_20201116_1437.py | 7 +- .../migrations/0015_auto_20201117_1739.py | 1 - .../migrations/0016_auto_20201119_1448.py | 9 +- .../0017_sponsorbenefit_added_by_user.py | 1 - .../migrations/0018_auto_20201201_1659.py | 5 +- .../migrations/0019_sponsor_twitter_handle.py | 1 - sponsors/migrations/0019_statementofwork.py | 1 - .../migrations/0020_auto_20201210_1802.py | 5 +- .../0020_sponsorshipbenefit_unavailable.py | 1 - .../migrations/0021_auto_20201211_2120.py | 1 - .../0022_sponsorcontact_administrative.py | 1 - .../migrations/0023_merge_20210406_1522.py | 8 +- .../migrations/0024_auto_20210414_1449.py | 7 +- .../migrations/0025_auto_20210416_1939.py | 67 ++- .../migrations/0026_auto_20210416_1940.py | 15 +- .../0027_sponsorbenefit_program_name.py | 14 +- .../migrations/0028_auto_20210707_1426.py | 14 +- .../migrations/0029_auto_20210715_2015.py | 80 ++- .../migrations/0030_auto_20210715_2023.py | 92 +++- .../migrations/0031_auto_20210810_1232.py | 20 +- .../0032_sponsorcontact_accounting.py | 11 +- ...redquantity_tieredquantityconfiguration.py | 1 - .../migrations/0034_contract_document_docx.py | 11 +- .../migrations/0035_auto_20210826_1929.py | 23 +- .../migrations/0036_auto_20210826_1930.py | 7 +- .../migrations/0037_sponsorship_package.py | 11 +- .../migrations/0038_auto_20210827_1223.py | 11 +- .../migrations/0039_auto_20210827_1248.py | 14 +- .../migrations/0040_auto_20210827_1313.py | 18 +- .../migrations/0041_auto_20210827_1313.py | 7 +- .../migrations/0042_auto_20210827_1318.py | 11 +- .../migrations/0043_auto_20210827_1343.py | 9 +- .../migrations/0044_auto_20210827_1344.py | 15 +- .../0045_add_added_by_user_sponsorbenefit.py | 9 +- .../0046_sponsorshippackage_advertise.py | 11 +- .../migrations/0047_auto_20210908_1357.py | 7 +- .../migrations/0048_auto_20210915_1425.py | 13 +- .../0049_sponsoremailnotificationtemplate.py | 21 +- ...targetable_emailtargetableconfiguration.py | 51 +- .../migrations/0051_auto_20211022_1403.py | 73 ++- ...dimgasset_requiredimgassetconfiguration.py | 109 +++- .../migrations/0053_genericasset_imgasset.py | 57 +- .../migrations/0054_auto_20211026_1432.py | 7 +- .../migrations/0055_auto_20211026_1512.py | 135 ++++- sponsors/migrations/0056_textasset.py | 25 +- .../migrations/0057_auto_20211026_1529.py | 7 +- .../migrations/0058_auto_20211029_1427.py | 55 +- .../migrations/0059_auto_20211029_1503.py | 7 +- .../migrations/0060_auto_20211111_1526.py | 19 +- .../migrations/0061_auto_20211108_1419.py | 57 +- .../migrations/0062_auto_20211111_1529.py | 41 +- .../migrations/0063_auto_20211220_1422.py | 29 +- .../0064_sponsorshippackage_slug.py | 9 +- .../migrations/0065_auto_20211223_1309.py | 7 +- .../migrations/0066_auto_20211223_1318.py | 9 +- .../0067_sponsorbenefit_a_la_carte.py | 9 +- .../migrations/0068_auto_20220110_1841.py | 12 +- .../migrations/0069_auto_20220110_2148.py | 133 ++++- .../migrations/0070_auto_20220111_2055.py | 171 ++++-- .../migrations/0071_auto_20220113_1843.py | 19 +- .../migrations/0072_auto_20220125_2005.py | 19 +- .../migrations/0073_auto_20220128_1906.py | 159 ++++-- .../migrations/0074_auto_20220211_1659.py | 11 +- .../migrations/0075_auto_20220303_2023.py | 9 +- .../migrations/0076_auto_20220728_1550.py | 51 +- .../migrations/0077_sponsorshipcurrentyear.py | 21 +- .../0078_init_current_year_singleton.py | 7 +- .../0079_index_to_force_singleton.py | 7 +- .../migrations/0080_auto_20220728_1644.py | 19 +- .../0081_sponsorship_application_year.py | 15 +- .../migrations/0082_auto_20220729_1613.py | 9 +- .../migrations/0083_auto_20220729_1624.py | 27 +- .../0084_init_configured_objs_year.py | 9 +- .../migrations/0085_auto_20220730_0945.py | 42 +- .../migrations/0086_auto_20220809_1655.py | 7 +- .../migrations/0087_auto_20220810_1647.py | 21 +- .../migrations/0088_auto_20220810_1655.py | 25 +- .../migrations/0089_auto_20220812_1312.py | 15 +- .../migrations/0090_auto_20220812_1314.py | 19 +- ...091_sponsorshippackage_allow_a_la_carte.py | 11 +- .../migrations/0092_auto_20220816_1517.py | 13 +- .../migrations/0093_auto_20230214_2113.py | 13 +- .../migrations/0094_sponsorship_locked.py | 13 +- .../migrations/0096_auto_20231214_2108.py | 49 +- .../migrations/0097_sponsorship_renewal.py | 7 +- .../migrations/0098_auto_20231219_1910.py | 13 +- .../migrations/0099_auto_20231224_1854.py | 16 +- .../migrations/0100_auto_20240107_1054.py | 35 +- .../0101_sponsor_linked_in_page_url.py | 11 +- .../migrations/0102_auto_20240509_2037.py | 9 +- ...nefitfeature_polymorphic_ctype_and_more.py | 76 ++- sponsors/models/__init__.py | 38 +- sponsors/models/assets.py | 8 +- sponsors/models/benefits.py | 94 ++-- sponsors/models/contract.py | 13 +- sponsors/models/managers.py | 33 +- sponsors/models/sponsors.py | 79 ++- sponsors/models/sponsorship.py | 124 ++--- sponsors/notifications.py | 8 +- sponsors/pandoc_filters/pagebreak.py | 28 +- sponsors/serializers.py | 2 +- sponsors/templatetags/sponsors.py | 38 +- sponsors/tests/baker_recipes.py | 4 +- sponsors/tests/test_admin.py | 7 +- sponsors/tests/test_api.py | 62 ++- sponsors/tests/test_contracts.py | 7 +- sponsors/tests/test_forms.py | 142 ++--- sponsors/tests/test_management_command.py | 12 +- sponsors/tests/test_managers.py | 54 +- sponsors/tests/test_models.py | 186 +++---- sponsors/tests/test_notifications.py | 74 +-- sponsors/tests/test_templatetags.py | 24 +- sponsors/tests/test_use_cases.py | 93 ++-- sponsors/tests/test_views.py | 71 +-- sponsors/tests/test_views_admin.py | 264 +++------ sponsors/tests/utils.py | 4 +- sponsors/urls.py | 8 +- sponsors/use_cases.py | 25 +- sponsors/views.py | 24 +- sponsors/views_admin.py | 112 ++-- successstories/admin.py | 18 +- successstories/apps.py | 3 +- successstories/factories.py | 40 +- successstories/forms.py | 19 +- successstories/managers.py | 3 +- successstories/migrations/0001_initial.py | 116 ++-- .../migrations/0002_auto_20150416_1853.py | 19 +- .../migrations/0003_auto_20170720_1655.py | 13 +- .../migrations/0004_auto_20170724_0507.py | 9 +- .../migrations/0005_auto_20170726_0645.py | 9 +- .../migrations/0006_auto_20170726_0824.py | 104 ++-- .../migrations/0007_remove_story_weight.py | 7 +- .../migrations/0008_auto_20170821_2000.py | 7 +- .../migrations/0009_auto_20180705_0352.py | 11 +- .../migrations/0010_story_submitted_by.py | 11 +- .../migrations/0011_auto_20220127_1923.py | 11 +- ...ry_creator_alter_story_last_modified_by.py | 27 +- successstories/models.py | 66 +-- successstories/tests/test_forms.py | 43 +- successstories/tests/test_models.py | 11 +- successstories/tests/test_templatetags.py | 22 +- successstories/tests/test_utils.py | 12 +- successstories/tests/test_views.py | 194 ++++--- successstories/urls.py | 8 +- successstories/utils.py | 21 +- successstories/views.py | 19 +- users/actions.py | 35 +- users/admin.py | 58 +- users/apps.py | 5 +- users/factories.py | 40 +- users/forms.py | 86 ++- users/managers.py | 1 - users/migrations/0001_initial.py | 176 ++++-- users/migrations/0002_auto_20150416_1853.py | 20 +- users/migrations/0003_auto_20150503_2026.py | 11 +- users/migrations/0004_auto_20150503_2100.py | 25 +- users/migrations/0005_user_public_profile.py | 9 +- users/migrations/0006_auto_20150503_2124.py | 11 +- users/migrations/0007_auto_20150604_1555.py | 11 +- users/migrations/0008_auto_20170814_0301.py | 47 +- users/migrations/0009_auto_20170821_2000.py | 19 +- users/migrations/0010_auto_20170828_1906.py | 21 +- users/migrations/0011_auto_20170902_0930.py | 16 +- users/migrations/0012_usergroup.py | 27 +- users/migrations/0013_auto_20180705_0348.py | 18 +- users/migrations/0014_auto_20210801_2332.py | 19 +- .../migrations/0015_alter_user_first_name.py | 9 +- users/models.py | 55 +- users/templatetags/users_tags.py | 6 +- users/tests/test_forms.py | 176 +++--- users/tests/test_models.py | 22 +- users/tests/test_templatetags.py | 35 +- users/tests/test_views.py | 291 +++++----- users/urls.py | 28 +- users/views.py | 95 ++-- work_groups/admin.py | 55 +- work_groups/apps.py | 3 +- work_groups/migrations/0001_initial.py | 204 +++++-- .../migrations/0002_auto_20150604_2203.py | 9 +- .../migrations/0003_auto_20170821_2000.py | 19 +- .../migrations/0004_auto_20180705_0352.py | 7 +- .../0005_alter_workgroup_creator_and_more.py | 27 +- work_groups/models.py | 10 +- 451 files changed, 8702 insertions(+), 7332 deletions(-) create mode 100644 ruff.toml diff --git a/Makefile b/Makefile index dc296feb4..d12049c0e 100644 --- a/Makefile +++ b/Makefile @@ -56,3 +56,6 @@ test: .state/db-initialized docker_shell: .state/db-initialized docker-compose run --rm web /bin/bash + +fmt: Dockerfile dev-requirements.txt base-requirements.txt + docker-compose run --rm web ruff format . diff --git a/banners/apps.py b/banners/apps.py index 9e3aa2c34..e5fdfe72d 100644 --- a/banners/apps.py +++ b/banners/apps.py @@ -2,5 +2,4 @@ class BannersAppConfig(AppConfig): - - name = 'banners' + name = "banners" diff --git a/banners/migrations/0001_initial.py b/banners/migrations/0001_initial.py index a50adb59f..d2520c124 100644 --- a/banners/migrations/0001_initial.py +++ b/banners/migrations/0001_initial.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [] @@ -31,27 +30,19 @@ class Migration(migrations.Migration): ), ( "message", - models.CharField( - help_text="Message to display in the banner", max_length=2048 - ), + models.CharField(help_text="Message to display in the banner", max_length=2048), ), ( "link", - models.CharField( - help_text="Link the button will go to", max_length=1024 - ), + models.CharField(help_text="Link the button will go to", max_length=1024), ), ( "active", - models.BooleanField( - default=False, help_text="Make the banner active on the site" - ), + models.BooleanField(default=False, help_text="Make the banner active on the site"), ), ( "psf_pages_only", - models.BooleanField( - default=True, help_text="Display the banner on /psf pages only" - ), + models.BooleanField(default=True, help_text="Display the banner on /psf pages only"), ), ], ) diff --git a/banners/models.py b/banners/models.py index 87797573a..21f34a3a0 100644 --- a/banners/models.py +++ b/banners/models.py @@ -2,17 +2,8 @@ class Banner(models.Model): - - title = models.CharField( - max_length=1024, help_text="Text to display in the banner's button" - ) - message = models.CharField( - max_length=2048, help_text="Message to display in the banner" - ) + title = models.CharField(max_length=1024, help_text="Text to display in the banner's button") + message = models.CharField(max_length=2048, help_text="Message to display in the banner") link = models.CharField(max_length=1024, help_text="Link the button will go to") - active = models.BooleanField( - null=False, default=False, help_text="Make the banner active on the site" - ) - psf_pages_only = models.BooleanField( - null=False, default=True, help_text="Display the banner on /psf pages only" - ) + active = models.BooleanField(null=False, default=False, help_text="Make the banner active on the site") + psf_pages_only = models.BooleanField(null=False, default=True, help_text="Display the banner on /psf pages only") diff --git a/blogs/admin.py b/blogs/admin.py index e5fea1cfb..4229a4497 100644 --- a/blogs/admin.py +++ b/blogs/admin.py @@ -6,22 +6,20 @@ @admin.register(BlogEntry) class BlogEntryAdmin(admin.ModelAdmin): - list_display = ['title', 'pub_date'] - date_hierarchy = 'pub_date' - actions = ['sync_new_entries'] + list_display = ["title", "pub_date"] + date_hierarchy = "pub_date" + actions = ["sync_new_entries"] - @admin.action( - description="Sync new blog entries" - ) + @admin.action(description="Sync new blog entries") def sync_new_entries(self, request, queryset): - call_command('update_blogs') + call_command("update_blogs") self.message_user(request, "Blog entries updated.") - @admin.register(FeedAggregate) class FeedAggregateAdmin(admin.ModelAdmin): - list_display = ['name', 'slug', 'description'] - prepopulated_fields = {'slug': ('name',)} + list_display = ["name", "slug", "description"] + prepopulated_fields = {"slug": ("name",)} + admin.site.register(Feed) diff --git a/blogs/apps.py b/blogs/apps.py index 0c88608d1..5fe245275 100644 --- a/blogs/apps.py +++ b/blogs/apps.py @@ -2,5 +2,4 @@ class BlogsAppConfig(AppConfig): - - name = 'blogs' + name = "blogs" diff --git a/blogs/factories.py b/blogs/factories.py index e66c4b36c..5748bf280 100644 --- a/blogs/factories.py +++ b/blogs/factories.py @@ -7,11 +7,11 @@ def initial_data(): feed, _ = Feed.objects.get_or_create( id=1, defaults={ - 'name': 'Python Insider', - 'website_url': settings.PYTHON_BLOG_URL, - 'feed_url': settings.PYTHON_BLOG_FEED_URL, - } + "name": "Python Insider", + "website_url": settings.PYTHON_BLOG_URL, + "feed_url": settings.PYTHON_BLOG_FEED_URL, + }, ) return { - 'feeds': [feed], + "feeds": [feed], } diff --git a/blogs/management/commands/update_blogs.py b/blogs/management/commands/update_blogs.py index 8914d0a78..4166e7cf1 100644 --- a/blogs/management/commands/update_blogs.py +++ b/blogs/management/commands/update_blogs.py @@ -6,16 +6,18 @@ class Command(BaseCommand): - """ Update blog entries and related blog feed data """ + """Update blog entries and related blog feed data""" def handle(self, **options): for feed in Feed.objects.all(): entries = get_all_entries(feed.feed_url) for entry in entries: - url = entry.pop('url') + url = entry.pop("url") BlogEntry.objects.update_or_create( - feed=feed, url=url, defaults=entry, + feed=feed, + url=url, + defaults=entry, ) feed.last_import = now() diff --git a/blogs/migrations/0001_initial.py b/blogs/migrations/0001_initial.py index 0694934cc..2074588ce 100644 --- a/blogs/migrations/0001_initial.py +++ b/blogs/migrations/0001_initial.py @@ -4,114 +4,170 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='BlogEntry', + name="BlogEntry", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=200)), - ('summary', models.TextField(blank=True)), - ('pub_date', models.DateTimeField()), - ('url', models.URLField(verbose_name='URL')), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("title", models.CharField(max_length=200)), + ("summary", models.TextField(blank=True)), + ("pub_date", models.DateTimeField()), + ("url", models.URLField(verbose_name="URL")), ], options={ - 'verbose_name': 'Blog Entry', - 'verbose_name_plural': 'Blog Entries', - 'get_latest_by': 'pub_date', + "verbose_name": "Blog Entry", + "verbose_name_plural": "Blog Entries", + "get_latest_by": "pub_date", }, bases=(models.Model,), ), migrations.CreateModel( - name='Contributor', + name="Contributor", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='blogs_contributor_creator', blank=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='blogs_contributor_modified', blank=True, on_delete=models.CASCADE)), - ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='blog_contributor', on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="blogs_contributor_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="blogs_contributor_modified", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "user", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, related_name="blog_contributor", on_delete=models.CASCADE + ), + ), ], options={ - 'verbose_name': 'Contributor', - 'verbose_name_plural': 'Contributors', - 'ordering': ('user__last_name', 'user__first_name'), + "verbose_name": "Contributor", + "verbose_name_plural": "Contributors", + "ordering": ("user__last_name", "user__first_name"), }, bases=(models.Model,), ), migrations.CreateModel( - name='Feed', + name="Feed", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200)), - ('website_url', models.URLField()), - ('feed_url', models.URLField()), - ('last_import', models.DateTimeField(null=True, blank=True)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=200)), + ("website_url", models.URLField()), + ("feed_url", models.URLField()), + ("last_import", models.DateTimeField(null=True, blank=True)), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='FeedAggregate', + name="FeedAggregate", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200)), - ('slug', models.SlugField(unique=True)), - ('description', models.TextField(help_text='Where this appears on the site')), - ('feeds', models.ManyToManyField(to='blogs.Feed')), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=200)), + ("slug", models.SlugField(unique=True)), + ("description", models.TextField(help_text="Where this appears on the site")), + ("feeds", models.ManyToManyField(to="blogs.Feed")), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='RelatedBlog', + name="RelatedBlog", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('name', models.CharField(help_text='Internal Name', max_length=100)), - ('feed_url', models.URLField(verbose_name='Feed URL')), - ('blog_url', models.URLField(verbose_name='Blog URL')), - ('blog_name', models.CharField(help_text='Displayed Name', max_length=200)), - ('last_entry_published', models.DateTimeField(db_index=True)), - ('last_entry_title', models.CharField(max_length=500)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='blogs_relatedblog_creator', blank=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='blogs_relatedblog_modified', blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("name", models.CharField(help_text="Internal Name", max_length=100)), + ("feed_url", models.URLField(verbose_name="Feed URL")), + ("blog_url", models.URLField(verbose_name="Blog URL")), + ("blog_name", models.CharField(help_text="Displayed Name", max_length=200)), + ("last_entry_published", models.DateTimeField(db_index=True)), + ("last_entry_title", models.CharField(max_length=500)), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="blogs_relatedblog_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="blogs_relatedblog_modified", + blank=True, + on_delete=models.CASCADE, + ), + ), ], options={ - 'verbose_name': 'Related Blog', - 'verbose_name_plural': 'Related Blogs', + "verbose_name": "Related Blog", + "verbose_name_plural": "Related Blogs", }, bases=(models.Model,), ), migrations.CreateModel( - name='Translation', + name="Translation", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('name', models.CharField(max_length=100)), - ('url', models.URLField(verbose_name='URL')), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='blogs_translation_creator', blank=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='blogs_translation_modified', blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("name", models.CharField(max_length=100)), + ("url", models.URLField(verbose_name="URL")), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="blogs_translation_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="blogs_translation_modified", + blank=True, + on_delete=models.CASCADE, + ), + ), ], options={ - 'verbose_name': 'Translation', - 'verbose_name_plural': 'Translations', - 'ordering': ('name',), + "verbose_name": "Translation", + "verbose_name_plural": "Translations", + "ordering": ("name",), }, bases=(models.Model,), ), migrations.AddField( - model_name='blogentry', - name='feed', - field=models.ForeignKey(to='blogs.Feed', on_delete=models.CASCADE), + model_name="blogentry", + name="feed", + field=models.ForeignKey(to="blogs.Feed", on_delete=models.CASCADE), preserve_default=True, ), ] diff --git a/blogs/migrations/0002_remove_translations_and_contributors.py b/blogs/migrations/0002_remove_translations_and_contributors.py index 644d691d1..d51a77389 100644 --- a/blogs/migrations/0002_remove_translations_and_contributors.py +++ b/blogs/migrations/0002_remove_translations_and_contributors.py @@ -4,36 +4,35 @@ class Migration(migrations.Migration): - dependencies = [ - ('blogs', '0001_initial'), + ("blogs", "0001_initial"), ] operations = [ migrations.RemoveField( - model_name='contributor', - name='creator', + model_name="contributor", + name="creator", ), migrations.RemoveField( - model_name='contributor', - name='last_modified_by', + model_name="contributor", + name="last_modified_by", ), migrations.RemoveField( - model_name='contributor', - name='user', + model_name="contributor", + name="user", ), migrations.RemoveField( - model_name='translation', - name='creator', + model_name="translation", + name="creator", ), migrations.RemoveField( - model_name='translation', - name='last_modified_by', + model_name="translation", + name="last_modified_by", ), migrations.DeleteModel( - name='Contributor', + name="Contributor", ), migrations.DeleteModel( - name='Translation', + name="Translation", ), ] diff --git a/blogs/migrations/0003_alter_relatedblog_creator_and_more.py b/blogs/migrations/0003_alter_relatedblog_creator_and_more.py index 9e71084a8..6d609e44c 100644 --- a/blogs/migrations/0003_alter_relatedblog_creator_and_more.py +++ b/blogs/migrations/0003_alter_relatedblog_creator_and_more.py @@ -6,21 +6,32 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('blogs', '0002_remove_translations_and_contributors'), + ("blogs", "0002_remove_translations_and_contributors"), ] operations = [ migrations.AlterField( - model_name='relatedblog', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="relatedblog", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='relatedblog', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="relatedblog", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/blogs/parser.py b/blogs/parser.py index fd5e4b54d..6167c684d 100644 --- a/blogs/parser.py +++ b/blogs/parser.py @@ -10,20 +10,18 @@ def get_all_entries(feed_url): - """ Retrieve all entries from a feed URL """ + """Retrieve all entries from a feed URL""" d = feedparser.parse(feed_url) entries = [] - for e in d['entries']: - published = make_aware( - datetime.datetime(*e['published_parsed'][:7]), timezone=datetime.timezone.utc - ) + for e in d["entries"]: + published = make_aware(datetime.datetime(*e["published_parsed"][:7]), timezone=datetime.timezone.utc) entry = { - 'title': e['title'], - 'summary': e.get('summary', ''), - 'pub_date': published, - 'url': e['link'], + "title": e["title"], + "summary": e.get("summary", ""), + "pub_date": published, + "url": e["link"], } entries.append(entry) @@ -32,12 +30,12 @@ def get_all_entries(feed_url): def _render_blog_supernav(entry): - """ Utility to make testing update_blogs management command easier """ - return render_to_string('blogs/supernav.html', {'entry': entry}) + """Utility to make testing update_blogs management command easier""" + return render_to_string("blogs/supernav.html", {"entry": entry}) def update_blog_supernav(): - """Retrieve latest entry and update blog supernav item """ + """Retrieve latest entry and update blog supernav item""" try: latest_entry = BlogEntry.objects.filter( feed=Feed.objects.get( @@ -49,9 +47,9 @@ def update_blog_supernav(): else: rendered_box = _render_blog_supernav(latest_entry) box, _ = Box.objects.update_or_create( - label='supernav-python-blog', + label="supernav-python-blog", defaults={ - 'content': rendered_box, - 'content_markup_type': 'html', - } + "content": rendered_box, + "content_markup_type": "html", + }, ) diff --git a/blogs/templatetags/blogs.py b/blogs/templatetags/blogs.py index bc055312e..00fa8d46e 100644 --- a/blogs/templatetags/blogs.py +++ b/blogs/templatetags/blogs.py @@ -7,7 +7,7 @@ @register.simple_tag def get_latest_blog_entries(limit=5): - """ Return limit of latest blog entries """ + """Return limit of latest blog entries""" return BlogEntry.objects.order_by("-pub_date")[:limit] @@ -21,6 +21,4 @@ def feed_list(slug, limit=10): {{ entry }} {% endfor %} """ - return BlogEntry.objects.filter( - feed__feedaggregate__slug=slug).order_by('-pub_date')[:limit] - + return BlogEntry.objects.filter(feed__feedaggregate__slug=slug).order_by("-pub_date")[:limit] diff --git a/blogs/tests/test_models.py b/blogs/tests/test_models.py index 3c29299ca..a1e30ce42 100644 --- a/blogs/tests/test_models.py +++ b/blogs/tests/test_models.py @@ -5,20 +5,19 @@ class BlogModelTest(TestCase): - def test_blog_entry(self): now = timezone.now() b = BlogEntry.objects.create( - title='Test Entry', - summary='Test Summary', + title="Test Entry", + summary="Test Summary", pub_date=now, - url='http://www.revsys.com', + url="http://www.revsys.com", feed=Feed.objects.create( - name='psf blog', - website_url='psf.example.org', - feed_url='feed.psf.example.org', - ) + name="psf blog", + website_url="psf.example.org", + feed_url="feed.psf.example.org", + ), ) self.assertEqual(str(b), b.title) diff --git a/blogs/tests/test_parser.py b/blogs/tests/test_parser.py index fbfcfca38..a69407224 100644 --- a/blogs/tests/test_parser.py +++ b/blogs/tests/test_parser.py @@ -6,7 +6,6 @@ class BlogParserTest(unittest.TestCase): - @classmethod def setUpClass(cls): super().setUpClass() @@ -15,17 +14,13 @@ def setUpClass(cls): def test_entries(self): self.assertEqual(len(self.entries), 25) - self.assertEqual( - self.entries[0]['title'], - 'Introducing Electronic Contributor Agreements' - ) + self.assertEqual(self.entries[0]["title"], "Introducing Electronic Contributor Agreements") self.assertIn( - "We're happy to announce the new way to file a contributor " - "agreement: on the web at", - self.entries[0]['summary'] + "We're happy to announce the new way to file a contributor " "agreement: on the web at", + self.entries[0]["summary"], ) - self.assertIsInstance(self.entries[0]['pub_date'], datetime.datetime) + self.assertIsInstance(self.entries[0]["pub_date"], datetime.datetime) self.assertEqual( - self.entries[0]['url'], - 'http://feedproxy.google.com/~r/PythonInsider/~3/tGNCqyOiun4/introducing-electronic-contributor.html' + self.entries[0]["url"], + "http://feedproxy.google.com/~r/PythonInsider/~3/tGNCqyOiun4/introducing-electronic-contributor.html", ) diff --git a/blogs/tests/test_templatetags.py b/blogs/tests/test_templatetags.py index c26fbd3ea..48a8bce89 100644 --- a/blogs/tests/test_templatetags.py +++ b/blogs/tests/test_templatetags.py @@ -9,7 +9,6 @@ class BlogTemplateTagTest(TestCase): - def setUp(self): self.test_file_path = get_test_rss_path() @@ -18,52 +17,34 @@ def test_get_latest_entries(self): Test our assignment tag, also ends up testing the update_blogs management command """ - Feed.objects.create( - name='psf default', website_url='https://example.org', - feed_url=self.test_file_path) - call_command('update_blogs') + Feed.objects.create(name="psf default", website_url="https://example.org", feed_url=self.test_file_path) + call_command("update_blogs") entries = get_latest_blog_entries() self.assertEqual(len(entries), 5) - self.assertEqual( - entries[0].pub_date.isoformat(), - '2013-03-04T15:00:00+00:00' - ) + self.assertEqual(entries[0].pub_date.isoformat(), "2013-03-04T15:00:00+00:00") def test_feed_list(self): f1 = Feed.objects.create( - name='psf blog', - website_url='psf.example.org', - feed_url='feed.psf.example.org', - ) - BlogEntry.objects.create( - title='test1', - summary='', - pub_date=now(), - url='path/to/foo', - feed=f1 + name="psf blog", + website_url="psf.example.org", + feed_url="feed.psf.example.org", ) + BlogEntry.objects.create(title="test1", summary="", pub_date=now(), url="path/to/foo", feed=f1) f2 = Feed.objects.create( - name='django blog', - website_url='django.example.org', - feed_url='feed.django.example.org', - ) - BlogEntry.objects.create( - title='test2', - summary='', - pub_date=now(), - url='path/to/foo', - feed=f2 + name="django blog", + website_url="django.example.org", + feed_url="feed.django.example.org", ) + BlogEntry.objects.create(title="test2", summary="", pub_date=now(), url="path/to/foo", feed=f2) fa = FeedAggregate.objects.create( - name='test', - slug='test', - description='testing', + name="test", + slug="test", + description="testing", ) fa.feeds.add(f1, f2) - t = Template(""" {% load blogs %} {% feed_list 'test' as entries %} @@ -73,4 +54,4 @@ def test_feed_list(self): """) rendered = t.render(Context()) - self.assertEqual(rendered.strip().replace(' ', ''), 'test2\n\ntest1') + self.assertEqual(rendered.strip().replace(" ", ""), "test2\n\ntest1") diff --git a/blogs/tests/test_views.py b/blogs/tests/test_views.py index 5c6c5053f..ca624e084 100644 --- a/blogs/tests/test_views.py +++ b/blogs/tests/test_views.py @@ -8,7 +8,6 @@ class BlogViewTest(TestCase): - def setUp(self): self.test_file_path = get_test_rss_path() @@ -17,13 +16,11 @@ def test_blog_home(self): Test our assignment tag, also ends up testing the update_blogs management command """ - Feed.objects.create( - id=1, name='psf default', website_url='example.org', - feed_url=self.test_file_path) - call_command('update_blogs') + Feed.objects.create(id=1, name="psf default", website_url="example.org", feed_url=self.test_file_path) + call_command("update_blogs") - resp = self.client.get(reverse('blog')) + resp = self.client.get(reverse("blog")) self.assertEqual(resp.status_code, 200) latest = BlogEntry.objects.latest() - self.assertEqual(resp.context['latest_entry'], latest) + self.assertEqual(resp.context["latest_entry"], latest) diff --git a/blogs/tests/utils.py b/blogs/tests/utils.py index abd2b412c..a7fe9296c 100644 --- a/blogs/tests/utils.py +++ b/blogs/tests/utils.py @@ -2,4 +2,4 @@ def get_test_rss_path(): - return os.path.join(os.path.dirname(__file__), 'psf_feed_example.xml') + return os.path.join(os.path.dirname(__file__), "psf_feed_example.xml") diff --git a/blogs/urls.py b/blogs/urls.py index d315ed486..f1ad74c87 100644 --- a/blogs/urls.py +++ b/blogs/urls.py @@ -2,5 +2,5 @@ from django.urls import path urlpatterns = [ - path('', views.BlogHome.as_view(), name='blog'), + path("", views.BlogHome.as_view(), name="blog"), ] diff --git a/blogs/views.py b/blogs/views.py index 2a018c0a5..ed0b0101c 100644 --- a/blogs/views.py +++ b/blogs/views.py @@ -4,13 +4,14 @@ class BlogHome(TemplateView): - """ Main blog view """ - template_name = 'blogs/index.html' + """Main blog view""" + + template_name = "blogs/index.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - entries = BlogEntry.objects.order_by('-pub_date')[:6] + entries = BlogEntry.objects.order_by("-pub_date")[:6] latest_entry = None other_entries = [] @@ -18,9 +19,11 @@ def get_context_data(self, **kwargs): latest_entry = entries[0] other_entries = entries[1:] - context.update({ - 'latest_entry': latest_entry, - 'entries': other_entries, - }) + context.update( + { + "latest_entry": latest_entry, + "entries": other_entries, + } + ) return context diff --git a/boxes/admin.py b/boxes/admin.py index d71e7810a..f26cb16dd 100644 --- a/boxes/admin.py +++ b/boxes/admin.py @@ -5,4 +5,4 @@ @admin.register(Box) class BoxAdmin(ContentManageableModelAdmin): - ordering = ('label', ) + ordering = ("label",) diff --git a/boxes/apps.py b/boxes/apps.py index 6f7e158e2..58220db9a 100644 --- a/boxes/apps.py +++ b/boxes/apps.py @@ -2,5 +2,4 @@ class BoxesAppConfig(AppConfig): - - name = 'boxes' + name = "boxes" diff --git a/boxes/factories.py b/boxes/factories.py index 5d8c7f2ad..6eae935d4 100644 --- a/boxes/factories.py +++ b/boxes/factories.py @@ -12,29 +12,28 @@ class BoxFactory(DjangoModelFactory): - class Meta: model = Box - django_get_or_create = ('label',) + django_get_or_create = ("label",) creator = factory.SubFactory(UserFactory) - content = factory.Faker('sentence', nb_words=10) + content = factory.Faker("sentence", nb_words=10) def initial_data(): boxes = [] fixtures_dir = pathlib.Path(settings.FIXTURE_DIRS[0]) - boxes_json = fixtures_dir / 'boxes.json' + boxes_json = fixtures_dir / "boxes.json" with boxes_json.open() as f: data = json.loads(f.read()) for d in data: - fields = d['fields'] + fields = d["fields"] box = BoxFactory( - label=fields['label'], - content_markup_type=fields['content_markup_type'], - content=fields['content'], + label=fields["label"], + content_markup_type=fields["content_markup_type"], + content=fields["content"], ) boxes.append(box) return { - 'boxes': boxes, + "boxes": boxes, } diff --git a/boxes/migrations/0001_initial.py b/boxes/migrations/0001_initial.py index 5a3261995..ddd94032e 100644 --- a/boxes/migrations/0001_initial.py +++ b/boxes/migrations/0001_initial.py @@ -5,27 +5,57 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='Box', + name="Box", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('label', models.SlugField(max_length=100, unique=True)), - ('content', markupfield.fields.MarkupField(rendered_field=True)), - ('content_markup_type', models.CharField(max_length=30, choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], default='restructuredtext')), - ('_content_rendered', models.TextField(editable=False)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='boxes_box_creator', blank=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='boxes_box_modified', blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("label", models.SlugField(max_length=100, unique=True)), + ("content", markupfield.fields.MarkupField(rendered_field=True)), + ( + "content_markup_type", + models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + default="restructuredtext", + ), + ), + ("_content_rendered", models.TextField(editable=False)), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="boxes_box_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="boxes_box_modified", + blank=True, + on_delete=models.CASCADE, + ), + ), ], options={ - 'verbose_name_plural': 'boxes', + "verbose_name_plural": "boxes", }, bases=(models.Model,), ), diff --git a/boxes/migrations/0002_auto_20150416_1853.py b/boxes/migrations/0002_auto_20150416_1853.py index ce4b6280a..5c6b7b850 100644 --- a/boxes/migrations/0002_auto_20150416_1853.py +++ b/boxes/migrations/0002_auto_20150416_1853.py @@ -2,16 +2,25 @@ class Migration(migrations.Migration): - dependencies = [ - ('boxes', '0001_initial'), + ("boxes", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='box', - name='content_markup_type', - field=models.CharField(max_length=30, default='restructuredtext', choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')]), + model_name="box", + name="content_markup_type", + field=models.CharField( + max_length=30, + default="restructuredtext", + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + ), preserve_default=True, ), ] diff --git a/boxes/migrations/0003_auto_20171101_2138.py b/boxes/migrations/0003_auto_20171101_2138.py index dc87d1627..89ce68ae0 100644 --- a/boxes/migrations/0003_auto_20171101_2138.py +++ b/boxes/migrations/0003_auto_20171101_2138.py @@ -4,23 +4,22 @@ def migrate_old_content(apps, schema_editor): - Box = apps.get_model('boxes', 'Box') - Box.objects.filter(label='events-subscriptions').update(content= - '

Python Events Calendars

\r\n\r\n
\r\n\r\n' - '

For Python events near you, please have a look at the ' - 'Python events map.

\r\n\r\n' - '

The Python events calendars are maintained by the events calendar team.

\r\n\r\n' - '

Please see the ' - 'events calendar project page for details on how to submit events,' - 'subscribe to the calendars,' - 'get Twitter feeds or embed them.

\r\n\r\n

Thank you.

\r\n' + Box = apps.get_model("boxes", "Box") + Box.objects.filter(label="events-subscriptions").update( + content='

Python Events Calendars

\r\n\r\n
\r\n\r\n' + '

For Python events near you, please have a look at the ' + "Python events map.

\r\n\r\n" + '

The Python events calendars are maintained by the events calendar team.

\r\n\r\n' + '

Please see the ' + 'events calendar project page for details on how to submit events,' + 'subscribe to the calendars,' + 'get Twitter feeds or embed them.

\r\n\r\n

Thank you.

\r\n' ) class Migration(migrations.Migration): - dependencies = [ - ('boxes', '0002_auto_20150416_1853'), + ("boxes", "0002_auto_20150416_1853"), ] operations = [ diff --git a/boxes/migrations/0004_alter_box_creator_alter_box_last_modified_by.py b/boxes/migrations/0004_alter_box_creator_alter_box_last_modified_by.py index 3829382ec..e01b1b9a9 100644 --- a/boxes/migrations/0004_alter_box_creator_alter_box_last_modified_by.py +++ b/boxes/migrations/0004_alter_box_creator_alter_box_last_modified_by.py @@ -6,21 +6,32 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('boxes', '0003_auto_20171101_2138'), + ("boxes", "0003_auto_20171101_2138"), ] operations = [ migrations.AlterField( - model_name='box', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="box", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='box', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="box", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/boxes/models.py b/boxes/models.py index b7b0a3385..a9ca7de64 100644 --- a/boxes/models.py +++ b/boxes/models.py @@ -13,7 +13,8 @@ from markupfield.fields import MarkupField from cms.models import ContentManageable -DEFAULT_MARKUP_TYPE = getattr(settings, 'DEFAULT_MARKUP_TYPE', 'restructuredtext') +DEFAULT_MARKUP_TYPE = getattr(settings, "DEFAULT_MARKUP_TYPE", "restructuredtext") + class Box(ContentManageable): label = models.SlugField(max_length=100, unique=True) @@ -23,4 +24,4 @@ def __str__(self): return self.label class Meta: - verbose_name_plural = 'boxes' + verbose_name_plural = "boxes" diff --git a/boxes/templatetags/boxes.py b/boxes/templatetags/boxes.py index 2beba5304..150e908ca 100644 --- a/boxes/templatetags/boxes.py +++ b/boxes/templatetags/boxes.py @@ -12,7 +12,7 @@ @register.simple_tag def box(label): try: - return mark_safe(Box.objects.only('content').get(label=label).content.rendered) + return mark_safe(Box.objects.only("content").get(label=label).content.rendered) except Box.DoesNotExist: - log.warning('WARNING: box not found: label=%s', label) - return '' + log.warning("WARNING: box not found: label=%s", label) + return "" diff --git a/boxes/tests.py b/boxes/tests.py index 13a8e998c..45c037b66 100644 --- a/boxes/tests.py +++ b/boxes/tests.py @@ -5,9 +5,11 @@ logging.disable(logging.CRITICAL) + class BaseTestCase(TestCase): def setUp(self): - self.box = Box.objects.create(label='test', content='test content') + self.box = Box.objects.create(label="test", content="test content") + class TemplateTagTests(BaseTestCase): def render(self, tmpl, **context): @@ -20,11 +22,11 @@ def test_tag(self): def test_tag_invalid_label(self): r = self.render('{% load boxes %}{% box "missing" %}') - self.assertEqual(r, '') + self.assertEqual(r, "") -class ViewTests(BaseTestCase): - @override_settings(ROOT_URLCONF='boxes.urls') +class ViewTests(BaseTestCase): + @override_settings(ROOT_URLCONF="boxes.urls") def test_box_view(self): - r = self.client.get('/test/') + r = self.client.get("/test/") self.assertContains(r, self.box.content.rendered) diff --git a/boxes/urls.py b/boxes/urls.py index 8ac457c08..55b63a515 100644 --- a/boxes/urls.py +++ b/boxes/urls.py @@ -2,5 +2,5 @@ from django.urls import path urlpatterns = [ - path('/', box, name='box'), + path("/", box, name="box"), ] diff --git a/boxes/views.py b/boxes/views.py index 02a50feb6..8e0584531 100644 --- a/boxes/views.py +++ b/boxes/views.py @@ -2,6 +2,7 @@ from django.shortcuts import get_object_or_404 from .models import Box + def box(request, label): b = get_object_or_404(Box, label=label) return HttpResponse(b.content.rendered) diff --git a/cms/admin.py b/cms/admin.py index 3468fe320..ac9315a62 100644 --- a/cms/admin.py +++ b/cms/admin.py @@ -26,15 +26,15 @@ def save_model(self, request, obj, form, change): def get_readonly_fields(self, request, obj=None): fields = list(super().get_readonly_fields(request, obj)) - return fields + ['created', 'updated', 'creator', 'last_modified_by'] + return fields + ["created", "updated", "creator", "last_modified_by"] def get_list_filter(self, request): fields = list(super().get_list_filter(request)) - return fields + ['created', 'updated'] + return fields + ["created", "updated"] def get_list_display(self, request): fields = list(super().get_list_display(request)) - return fields + ['created', 'updated'] + return fields + ["created", "updated"] def get_fieldsets(self, request, obj=None): """ @@ -45,16 +45,21 @@ def get_fieldsets(self, request, obj=None): # be there if the child class didn't manually declare fieldsets. fieldsets = super().get_fieldsets(request, obj) for name, fieldset in fieldsets: - for f in ('created', 'updated', 'creator', 'last_modified_by'): - if f in fieldset['fields']: - fieldset['fields'].remove(f) + for f in ("created", "updated", "creator", "last_modified_by"): + if f in fieldset["fields"]: + fieldset["fields"].remove(f) # Now add these fields to a collapsed fieldset at the end. # FIXME: better name than "CMS metadata", that sucks. - return fieldsets + [("CMS metadata", { - 'fields': [('creator', 'created'), ('last_modified_by', 'updated')], - 'classes': ('collapse',), - })] + return fieldsets + [ + ( + "CMS metadata", + { + "fields": [("creator", "created"), ("last_modified_by", "updated")], + "classes": ("collapse",), + }, + ) + ] class ContentManageableModelAdmin(ContentManageableAdmin, admin.ModelAdmin): diff --git a/cms/apps.py b/cms/apps.py index f9df91865..90c764d39 100644 --- a/cms/apps.py +++ b/cms/apps.py @@ -2,5 +2,4 @@ class CmsAppConfig(AppConfig): - - name = 'cms' + name = "cms" diff --git a/cms/management/commands/create_initial_data.py b/cms/management/commands/create_initial_data.py index b0147704b..6142dfca0 100644 --- a/cms/management/commands/create_initial_data.py +++ b/cms/management/commands/create_initial_data.py @@ -7,20 +7,19 @@ class Command(BaseCommand): - - help = 'Create initial data by using factories.' + help = "Create initial data by using factories." def add_arguments(self, parser): parser.add_argument( - '--app-label', - dest='app_label', - help='Provide an app label to create app specific data (e.g. --app-label boxes)', + "--app-label", + dest="app_label", + help="Provide an app label to create app specific data (e.g. --app-label boxes)", ) parser.add_argument( - '--flush', - action='store_true', - dest='do_flush', - help='Remove existing data in the database before creating new data.', + "--flush", + action="store_true", + dest="do_flush", + help="Remove existing data in the database before creating new data.", ) def collect_initial_data_functions(self, app_label): @@ -29,18 +28,18 @@ def collect_initial_data_functions(self, app_label): try: app_list = [apps.get_app_config(app_label)] except LookupError: - self.stdout.write(self.style.ERROR('The app label provided does not exist as an application.')) + self.stdout.write(self.style.ERROR("The app label provided does not exist as an application.")) return else: app_list = apps.get_app_configs() for app in app_list: try: - factory_module = importlib.import_module(f'{app.name}.factories') + factory_module = importlib.import_module(f"{app.name}.factories") except ImportError: continue else: for name, function in inspect.getmembers(factory_module, inspect.isfunction): - if name == 'initial_data': + if name == "initial_data": functions[app.name] = function break return functions @@ -48,53 +47,53 @@ def collect_initial_data_functions(self, app_label): def output(self, app_name, verbosity, *, done=False, result=False): if verbosity > 0: if done: - self.stdout.write(self.style.SUCCESS('DONE')) + self.stdout.write(self.style.SUCCESS("DONE")) else: - self.stdout.write(f'Creating initial data for {app_name!r}... ', ending='') + self.stdout.write(f"Creating initial data for {app_name!r}... ", ending="") if verbosity >= 2 and result: pprint.pprint(result) def flush_handler(self, do_flush, verbosity): if do_flush: msg = ( - 'You have provided the --flush argument, this will cleanup ' - 'the database before creating new data.\n' - 'Type \'y\' or \'yes\' to continue, \'n\' or \'no\' to cancel: ' - ) + "You have provided the --flush argument, this will cleanup " + "the database before creating new data.\n" + "Type 'y' or 'yes' to continue, 'n' or 'no' to cancel: " + ) else: msg = ( - 'Note that this command won\'t cleanup the database before ' - 'creating new data.\n' - 'If you would like to cleanup the database before creating ' - 'new data, call create_initial_data with --flush.\n' - 'Type \'y\' or \'yes\' to continue, \'n\' or \'no\' to cancel: ' + "Note that this command won't cleanup the database before " + "creating new data.\n" + "If you would like to cleanup the database before creating " + "new data, call create_initial_data with --flush.\n" + "Type 'y' or 'yes' to continue, 'n' or 'no' to cancel: " ) confirm = input(self.style.WARNING(msg)) - if do_flush and confirm in ('y', 'yes'): + if do_flush and confirm in ("y", "yes"): try: - call_command('flush', verbosity=verbosity, interactive=False) + call_command("flush", verbosity=verbosity, interactive=False) except Exception as exc: - self.stdout.write(self.style.ERROR(f'{type(exc).__name__}: {exc}')) + self.stdout.write(self.style.ERROR(f"{type(exc).__name__}: {exc}")) return confirm def handle(self, **options): - verbosity = options['verbosity'] - app_label = options['app_label'] - do_flush = options['do_flush'] + verbosity = options["verbosity"] + app_label = options["app_label"] + do_flush = options["do_flush"] confirm = self.flush_handler(do_flush, verbosity) - if confirm not in ('y', 'yes'): + if confirm not in ("y", "yes"): return # Special case '--app-label=sitetree'. - if not app_label or app_label == 'sitetree': - self.output('sitetree', verbosity) + if not app_label or app_label == "sitetree": + self.output("sitetree", verbosity) try: - call_command('loaddata', 'sitetree_menus', '-v0') + call_command("loaddata", "sitetree_menus", "-v0") except Exception as exc: - self.stdout.write(self.style.ERROR(f'{type(exc).__name__}: {exc}')) + self.stdout.write(self.style.ERROR(f"{type(exc).__name__}: {exc}")) else: - self.output('sitetree', verbosity, done=True) + self.output("sitetree", verbosity, done=True) # Collect relevant functions for data generation. functions = self.collect_initial_data_functions(app_label) @@ -106,7 +105,7 @@ def handle(self, **options): try: result = function() except Exception as exc: - self.stdout.write(self.style.ERROR(f'{type(exc).__name__}: {exc}')) + self.stdout.write(self.style.ERROR(f"{type(exc).__name__}: {exc}")) continue else: self.output(app_name, verbosity, done=True, result=result) diff --git a/cms/models.py b/cms/models.py index d59a380f1..70eff155e 100644 --- a/cms/models.py +++ b/cms/models.py @@ -25,14 +25,14 @@ class ContentManageable(models.Model): # where there isn't a request.user sitting around). creator = models.ForeignKey( settings.AUTH_USER_MODEL, - related_name='%(app_label)s_%(class)s_creator', + related_name="%(app_label)s_%(class)s_creator", null=True, blank=True, on_delete=models.CASCADE, ) last_modified_by = models.ForeignKey( settings.AUTH_USER_MODEL, - related_name='%(app_label)s_%(class)s_modified', + related_name="%(app_label)s_%(class)s_modified", null=True, blank=True, on_delete=models.CASCADE, diff --git a/cms/templatetags/cms.py b/cms/templatetags/cms.py index 99b616399..d194e43ef 100644 --- a/cms/templatetags/cms.py +++ b/cms/templatetags/cms.py @@ -4,11 +4,11 @@ register = template.Library() -@register.inclusion_tag('cms/iso_time_tag.html') +@register.inclusion_tag("cms/iso_time_tag.html") def iso_time_tag(date): return { - 'timestamp': format(date, 'c'), - 'month': format(date, 'm'), - 'day': format(date, 'd'), - 'year': format(date, 'Y'), + "timestamp": format(date, "c"), + "month": format(date, "m"), + "day": format(date, "d"), + "year": format(date, "Y"), } diff --git a/cms/tests.py b/cms/tests.py index 9c9e8a6d4..0a6dfa584 100644 --- a/cms/tests.py +++ b/cms/tests.py @@ -19,36 +19,34 @@ def make_admin(self, **kwargs): return cls(mock.Mock(), mock.Mock()) def test_readonly_fields(self): - admin = self.make_admin(readonly_fields=['f1']) + admin = self.make_admin(readonly_fields=["f1"]) self.assertEqual( - admin.get_readonly_fields(request=mock.Mock()), - ['f1', 'created', 'updated', 'creator', 'last_modified_by'] + admin.get_readonly_fields(request=mock.Mock()), ["f1", "created", "updated", "creator", "last_modified_by"] ) def test_list_filter(self): - admin = self.make_admin(list_filter=['f1']) - self.assertEqual( - admin.get_list_filter(request=mock.Mock()), - ['f1', 'created', 'updated'] - ) + admin = self.make_admin(list_filter=["f1"]) + self.assertEqual(admin.get_list_filter(request=mock.Mock()), ["f1", "created", "updated"]) def test_list_display(self): - admin = self.make_admin(list_display=['f1']) - self.assertEqual( - admin.get_list_display(request=mock.Mock()), - ['f1', 'created', 'updated'] - ) + admin = self.make_admin(list_display=["f1"]) + self.assertEqual(admin.get_list_display(request=mock.Mock()), ["f1", "created", "updated"]) def test_get_fieldsets(self): - admin = self.make_admin(fieldsets=[(None, {'fields': ['foo', 'created']})]) + admin = self.make_admin(fieldsets=[(None, {"fields": ["foo", "created"]})]) fieldsets = admin.get_fieldsets(request=mock.Mock()) # Check that "created" is removed from the specified fieldset and moved # into the automatic one. self.assertEqual( fieldsets, - [(None, {'fields': ['foo']}), - ('CMS metadata', {'fields': [('creator', 'created'), ('last_modified_by', 'updated')], 'classes': ('collapse',)})] + [ + (None, {"fields": ["foo"]}), + ( + "CMS metadata", + {"fields": [("creator", "created"), ("last_modified_by", "updated")], "classes": ("collapse",)}, + ), + ], ) def test_save_model(self): @@ -63,26 +61,29 @@ def test_update_model(self): request = mock.Mock() obj = mock.Mock() admin.save_model(request=request, obj=obj, form=None, change=True) - self.assertEqual(obj.last_modified_by, request.user, "save_model didn't set obj.last_modified_by to request.user") + self.assertEqual( + obj.last_modified_by, request.user, "save_model didn't set obj.last_modified_by to request.user" + ) class TemplateTagsTest(unittest.TestCase): def test_iso_time_tag(self): now = datetime.datetime(2014, 1, 1, 12, 0) template = Template("{% load cms %}{% iso_time_tag now %}") - rendered = template.render(Context({'now': now})) - self.assertIn('', rendered) + rendered = template.render(Context({"now": now})) + self.assertIn( + '', rendered + ) class Test404(TestCase): def test_legacy_path(self): - self.assertEqual(legacy_path('/any/thing'), 'http://legacy.python.org/any/thing') + self.assertEqual(legacy_path("/any/thing"), "http://legacy.python.org/any/thing") def test_custom_404(self): - """ Ensure custom 404 is set to 5 minutes """ - response = self.client.get('/foo-bar/baz/9876') + """Ensure custom 404 is set to 5 minutes""" + response = self.client.get("/foo-bar/baz/9876") self.assertEqual(response.status_code, 404) - self.assertEqual(response['Cache-Control'], 'max-age=300') - self.assertTemplateUsed('404.html') - self.assertContains(response, 'Try using the search box.', - status_code=404) + self.assertEqual(response["Cache-Control"], "max-age=300") + self.assertTemplateUsed("404.html") + self.assertContains(response, "Try using the search box.", status_code=404) diff --git a/cms/views.py b/cms/views.py index e3d938136..1b38358a4 100644 --- a/cms/views.py +++ b/cms/views.py @@ -2,8 +2,8 @@ from django.shortcuts import render from urllib.parse import urljoin -LEGACY_PYTHON_DOMAIN = 'http://legacy.python.org' -PYPI_URL = 'https://pypi.org/' +LEGACY_PYTHON_DOMAIN = "http://legacy.python.org" +PYPI_URL = "https://pypi.org/" def legacy_path(path): @@ -11,14 +11,14 @@ def legacy_path(path): return urljoin(LEGACY_PYTHON_DOMAIN, path) -def custom_404(request, exception, template_name='404.html'): +def custom_404(request, exception, template_name="404.html"): """Custom 404 handler to only cache 404s for 5 minutes.""" context = { - 'legacy_path': legacy_path(request.path), - 'download_path': reverse('download:download'), - 'doc_path': reverse('documentation'), - 'pypi_path': PYPI_URL, + "legacy_path": legacy_path(request.path), + "download_path": reverse("download:download"), + "doc_path": reverse("documentation"), + "pypi_path": PYPI_URL, } response = render(request, template_name, context=context, status=404) - response['Cache-Control'] = 'max-age=300' + response["Cache-Control"] = "max-age=300" return response diff --git a/codesamples/apps.py b/codesamples/apps.py index 565ae1f8b..c442d6bf6 100644 --- a/codesamples/apps.py +++ b/codesamples/apps.py @@ -2,5 +2,4 @@ class CodesamplesAppConfig(AppConfig): - - name = 'codesamples' + name = "codesamples" diff --git a/codesamples/factories.py b/codesamples/factories.py index 5a52b9738..c80a87f65 100644 --- a/codesamples/factories.py +++ b/codesamples/factories.py @@ -9,16 +9,15 @@ class CodeSampleFactory(DjangoModelFactory): - class Meta: model = CodeSample - django_get_or_create = ('code',) + django_get_or_create = ("code",) creator = factory.SubFactory(UserFactory) - code = factory.Faker('sentence', nb_words=10) - code_markup_type = 'html' - copy = factory.Faker('sentence', nb_words=10) - copy_markup_type = 'html' + code = factory.Faker("sentence", nb_words=10) + code_markup_type = "html" + copy = factory.Faker("sentence", nb_words=10) + copy_markup_type = "html" is_published = True @@ -45,7 +44,7 @@ def initial_data(): easy to learn. Whet your appetite with our Python overview.

- """ + """, ), ( """\ @@ -68,7 +67,7 @@ def initial_data(): More about simple math functions.

- """ + """, ), ( """\ @@ -89,7 +88,7 @@ def initial_data(): sliced and manipulated with other built-in functions. More about lists

- """ + """, ), ( """\ @@ -114,7 +113,7 @@ def initial_data(): its own twists, of course. More control flow tools

- """ + """, ), ( """\ @@ -139,13 +138,15 @@ def initial_data(): and even arbitrary argument lists. More about defining functions

- """ + """, ), ] return { - 'boxes': [ + "boxes": [ CodeSampleFactory( - code=textwrap.dedent(code), copy=textwrap.dedent(copy), - ) for code, copy in code_samples + code=textwrap.dedent(code), + copy=textwrap.dedent(copy), + ) + for code, copy in code_samples ], } diff --git a/codesamples/migrations/0001_initial.py b/codesamples/migrations/0001_initial.py index 5727acfbe..b5ab53f3a 100644 --- a/codesamples/migrations/0001_initial.py +++ b/codesamples/migrations/0001_initial.py @@ -5,31 +5,76 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='CodeSample', + name="CodeSample", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('code', markupfield.fields.MarkupField(rendered_field=True, blank=True)), - ('code_markup_type', models.CharField(max_length=30, choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], default='html', blank=True)), - ('copy', markupfield.fields.MarkupField(rendered_field=True, blank=True)), - ('_code_rendered', models.TextField(editable=False)), - ('copy_markup_type', models.CharField(max_length=30, choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], default='html', blank=True)), - ('is_published', models.BooleanField(db_index=True, default=False)), - ('_copy_rendered', models.TextField(editable=False)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='codesamples_codesample_creator', blank=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='codesamples_codesample_modified', blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("code", markupfield.fields.MarkupField(rendered_field=True, blank=True)), + ( + "code_markup_type", + models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + default="html", + blank=True, + ), + ), + ("copy", markupfield.fields.MarkupField(rendered_field=True, blank=True)), + ("_code_rendered", models.TextField(editable=False)), + ( + "copy_markup_type", + models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + default="html", + blank=True, + ), + ), + ("is_published", models.BooleanField(db_index=True, default=False)), + ("_copy_rendered", models.TextField(editable=False)), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="codesamples_codesample_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="codesamples_codesample_modified", + blank=True, + on_delete=models.CASCADE, + ), + ), ], options={ - 'verbose_name': 'sample', - 'verbose_name_plural': 'samples', + "verbose_name": "sample", + "verbose_name_plural": "samples", }, bases=(models.Model,), ), diff --git a/codesamples/migrations/0002_auto_20150416_1853.py b/codesamples/migrations/0002_auto_20150416_1853.py index d3fc256e8..87597e623 100644 --- a/codesamples/migrations/0002_auto_20150416_1853.py +++ b/codesamples/migrations/0002_auto_20150416_1853.py @@ -2,22 +2,43 @@ class Migration(migrations.Migration): - dependencies = [ - ('codesamples', '0001_initial'), + ("codesamples", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='codesample', - name='code_markup_type', - field=models.CharField(max_length=30, choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')], default='html', blank=True), + model_name="codesample", + name="code_markup_type", + field=models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + default="html", + blank=True, + ), preserve_default=True, ), migrations.AlterField( - model_name='codesample', - name='copy_markup_type', - field=models.CharField(max_length=30, choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')], default='html', blank=True), + model_name="codesample", + name="copy_markup_type", + field=models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + default="html", + blank=True, + ), preserve_default=True, ), ] diff --git a/codesamples/migrations/0003_auto_20170821_2000.py b/codesamples/migrations/0003_auto_20170821_2000.py index de9acb05e..628c0f670 100644 --- a/codesamples/migrations/0003_auto_20170821_2000.py +++ b/codesamples/migrations/0003_auto_20170821_2000.py @@ -2,20 +2,39 @@ class Migration(migrations.Migration): - dependencies = [ - ('codesamples', '0002_auto_20150416_1853'), + ("codesamples", "0002_auto_20150416_1853"), ] operations = [ migrations.AlterField( - model_name='codesample', - name='code_markup_type', - field=models.CharField(choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')], default='html', max_length=30), + model_name="codesample", + name="code_markup_type", + field=models.CharField( + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + default="html", + max_length=30, + ), ), migrations.AlterField( - model_name='codesample', - name='copy_markup_type', - field=models.CharField(choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')], default='html', max_length=30), + model_name="codesample", + name="copy_markup_type", + field=models.CharField( + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + default="html", + max_length=30, + ), ), ] diff --git a/codesamples/migrations/0004_alter_codesample_creator_and_more.py b/codesamples/migrations/0004_alter_codesample_creator_and_more.py index 0b29294ad..0863dace5 100644 --- a/codesamples/migrations/0004_alter_codesample_creator_and_more.py +++ b/codesamples/migrations/0004_alter_codesample_creator_and_more.py @@ -6,21 +6,32 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('codesamples', '0003_auto_20170821_2000'), + ("codesamples", "0003_auto_20170821_2000"), ] operations = [ migrations.AlterField( - model_name='codesample', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="codesample", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='codesample', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="codesample", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/codesamples/models.py b/codesamples/models.py index e0158fb69..4cfb206c9 100644 --- a/codesamples/models.py +++ b/codesamples/models.py @@ -8,7 +8,7 @@ from .managers import CodeSampleQuerySet -DEFAULT_MARKUP_TYPE = getattr(settings, 'DEFAULT_MARKUP_TYPE', 'html') +DEFAULT_MARKUP_TYPE = getattr(settings, "DEFAULT_MARKUP_TYPE", "html") class CodeSample(ContentManageable): @@ -19,8 +19,8 @@ class CodeSample(ContentManageable): objects = CodeSampleQuerySet.as_manager() class Meta: - verbose_name = 'sample' - verbose_name_plural = 'samples' + verbose_name = "sample" + verbose_name_plural = "samples" def __str__(self): return truncatechars(striptags(self.copy), 20) diff --git a/codesamples/tests.py b/codesamples/tests.py index 7ddf51119..bd8823aca 100644 --- a/codesamples/tests.py +++ b/codesamples/tests.py @@ -5,18 +5,12 @@ class CodeSampleModelTests(TestCase): def setUp(self): - self.sample2 = CodeSample.objects.create( - code='Code One', - copy='Copy One', - is_published=True) + self.sample2 = CodeSample.objects.create(code="Code One", copy="Copy One", is_published=True) - self.sample2 = CodeSample.objects.create( - code='Code Two', - copy='Copy Two', - is_published=False) + self.sample2 = CodeSample.objects.create(code="Code Two", copy="Copy Two", is_published=False) def test_published(self): - self.assertQuerySetEqual(CodeSample.objects.published(),[''], transform=repr) + self.assertQuerySetEqual(CodeSample.objects.published(), [""], transform=repr) def test_draft(self): - self.assertQuerySetEqual(CodeSample.objects.draft(),[''], transform=repr) + self.assertQuerySetEqual(CodeSample.objects.draft(), [""], transform=repr) diff --git a/community/admin.py b/community/admin.py index b9023ac00..5d84f10d3 100644 --- a/community/admin.py +++ b/community/admin.py @@ -21,9 +21,9 @@ class VideoInline(ContentManageableStackedInline): @admin.register(Post) class PostAdmin(ContentManageableModelAdmin): - date_hierarchy = 'created' - list_display = ['__str__', 'status', 'media_type'] - list_filter = ['status', 'media_type'] + date_hierarchy = "created" + list_display = ["__str__", "status", "media_type"] + list_filter = ["status", "media_type"] inlines = [ LinkInline, PhotoInline, @@ -33,5 +33,5 @@ class PostAdmin(ContentManageableModelAdmin): @admin.register(Link, Photo, Video) class PostTypeAdmin(ContentManageableModelAdmin): - date_hierarchy = 'created' - raw_id_fields = ['post'] + date_hierarchy = "created" + raw_id_fields = ["post"] diff --git a/community/apps.py b/community/apps.py index 7dee88ac0..2cdda8bef 100644 --- a/community/apps.py +++ b/community/apps.py @@ -2,5 +2,4 @@ class CommunityAppConfig(AppConfig): - - name = 'community' + name = "community" diff --git a/community/managers.py b/community/managers.py index 9c6baab83..60d8d6f05 100644 --- a/community/managers.py +++ b/community/managers.py @@ -2,11 +2,12 @@ class PostQuerySet(QuerySet): - def public(self): return self.filter(status__exact=self.model.STATUS_PUBLIC) def private(self): - return self.filter(status__in=[ - self.model.STATUS_PRIVATE, - ]) + return self.filter( + status__in=[ + self.model.STATUS_PRIVATE, + ] + ) diff --git a/community/migrations/0001_initial.py b/community/migrations/0001_initial.py index 291959764..83d68a423 100644 --- a/community/migrations/0001_initial.py +++ b/community/migrations/0001_initial.py @@ -6,109 +6,209 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='Link', + name="Link", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('url', models.URLField(max_length=1000, verbose_name='URL', blank=True)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='community_link_creator', blank=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='community_link_modified', blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("url", models.URLField(max_length=1000, verbose_name="URL", blank=True)), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="community_link_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="community_link_modified", + blank=True, + on_delete=models.CASCADE, + ), + ), ], options={ - 'verbose_name': 'Link', - 'verbose_name_plural': 'Links', - 'ordering': ['-created'], - 'get_latest_by': 'created', + "verbose_name": "Link", + "verbose_name_plural": "Links", + "ordering": ["-created"], + "get_latest_by": "created", }, bases=(models.Model,), ), migrations.CreateModel( - name='Photo', + name="Photo", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('image', models.ImageField(upload_to='community/photos/', blank=True)), - ('image_url', models.URLField(max_length=1000, verbose_name='Image URL', blank=True)), - ('caption', models.TextField(blank=True)), - ('click_through_url', models.URLField(blank=True)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='community_photo_creator', blank=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='community_photo_modified', blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("image", models.ImageField(upload_to="community/photos/", blank=True)), + ("image_url", models.URLField(max_length=1000, verbose_name="Image URL", blank=True)), + ("caption", models.TextField(blank=True)), + ("click_through_url", models.URLField(blank=True)), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="community_photo_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="community_photo_modified", + blank=True, + on_delete=models.CASCADE, + ), + ), ], options={ - 'verbose_name': 'photo', - 'verbose_name_plural': 'photos', - 'ordering': ['-created'], - 'get_latest_by': 'created', + "verbose_name": "photo", + "verbose_name_plural": "photos", + "ordering": ["-created"], + "get_latest_by": "created", }, bases=(models.Model,), ), migrations.CreateModel( - name='Post', + name="Post", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('title', models.CharField(max_length=200, blank=True, null=True)), - ('content', markupfield.fields.MarkupField(rendered_field=True)), - ('abstract', models.TextField(null=True, blank=True)), - ('content_markup_type', models.CharField(max_length=30, choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], default='html')), - ('_content_rendered', models.TextField(editable=False)), - ('media_type', models.IntegerField(choices=[(1, 'text'), (2, 'photo'), (3, 'video'), (4, 'link')], default=1)), - ('source_url', models.URLField(max_length=1000, blank=True)), - ('meta', django.contrib.postgres.fields.jsonb.JSONField(default={}, blank=True)), - ('status', models.IntegerField(db_index=True, choices=[(1, 'private'), (2, 'public')], default=1)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='community_post_creator', blank=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='community_post_modified', blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("title", models.CharField(max_length=200, blank=True, null=True)), + ("content", markupfield.fields.MarkupField(rendered_field=True)), + ("abstract", models.TextField(null=True, blank=True)), + ( + "content_markup_type", + models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + default="html", + ), + ), + ("_content_rendered", models.TextField(editable=False)), + ( + "media_type", + models.IntegerField(choices=[(1, "text"), (2, "photo"), (3, "video"), (4, "link")], default=1), + ), + ("source_url", models.URLField(max_length=1000, blank=True)), + ("meta", django.contrib.postgres.fields.jsonb.JSONField(default={}, blank=True)), + ("status", models.IntegerField(db_index=True, choices=[(1, "private"), (2, "public")], default=1)), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="community_post_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="community_post_modified", + blank=True, + on_delete=models.CASCADE, + ), + ), ], options={ - 'verbose_name': 'post', - 'verbose_name_plural': 'posts', - 'ordering': ['-created'], - 'get_latest_by': 'created', + "verbose_name": "post", + "verbose_name_plural": "posts", + "ordering": ["-created"], + "get_latest_by": "created", }, bases=(models.Model,), ), migrations.CreateModel( - name='Video', + name="Video", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('video_embed', models.TextField(blank=True)), - ('video_data', models.FileField(upload_to='community/videos/', blank=True)), - ('caption', models.TextField(blank=True)), - ('click_through_url', models.URLField(verbose_name='Click Through URL', blank=True)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='community_video_creator', blank=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='community_video_modified', blank=True, on_delete=models.CASCADE)), - ('post', models.ForeignKey(editable=False, null=True, to='community.Post', related_name='related_video', on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("video_embed", models.TextField(blank=True)), + ("video_data", models.FileField(upload_to="community/videos/", blank=True)), + ("caption", models.TextField(blank=True)), + ("click_through_url", models.URLField(verbose_name="Click Through URL", blank=True)), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="community_video_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="community_video_modified", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "post", + models.ForeignKey( + editable=False, + null=True, + to="community.Post", + related_name="related_video", + on_delete=models.CASCADE, + ), + ), ], options={ - 'verbose_name': 'video', - 'verbose_name_plural': 'videos', - 'ordering': ['-created'], - 'get_latest_by': 'created', + "verbose_name": "video", + "verbose_name_plural": "videos", + "ordering": ["-created"], + "get_latest_by": "created", }, bases=(models.Model,), ), migrations.AddField( - model_name='photo', - name='post', - field=models.ForeignKey(editable=False, null=True, to='community.Post', related_name='related_photo', on_delete=models.CASCADE), + model_name="photo", + name="post", + field=models.ForeignKey( + editable=False, null=True, to="community.Post", related_name="related_photo", on_delete=models.CASCADE + ), preserve_default=True, ), migrations.AddField( - model_name='link', - name='post', - field=models.ForeignKey(editable=False, null=True, to='community.Post', related_name='related_link', on_delete=models.CASCADE), + model_name="link", + name="post", + field=models.ForeignKey( + editable=False, null=True, to="community.Post", related_name="related_link", on_delete=models.CASCADE + ), preserve_default=True, ), ] diff --git a/community/migrations/0001_squashed_0004_auto_20170831_0541.py b/community/migrations/0001_squashed_0004_auto_20170831_0541.py index f709bc910..e43d15546 100644 --- a/community/migrations/0001_squashed_0004_auto_20170831_0541.py +++ b/community/migrations/0001_squashed_0004_auto_20170831_0541.py @@ -9,8 +9,12 @@ class Migration(migrations.Migration): - - replaces = [('community', '0001_initial'), ('community', '0002_auto_20150416_1853'), ('community', '0003_auto_20170831_0358'), ('community', '0004_auto_20170831_0541')] + replaces = [ + ("community", "0001_initial"), + ("community", "0002_auto_20150416_1853"), + ("community", "0003_auto_20170831_0358"), + ("community", "0004_auto_20170831_0541"), + ] initial = True @@ -20,111 +24,230 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Link', + name="Link", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(blank=True, db_index=True, default=django.utils.timezone.now)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('url', models.URLField(blank=True, max_length=1000, verbose_name='URL')), - ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='community_link_creator', to=settings.AUTH_USER_MODEL)), - ('last_modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='community_link_modified', to=settings.AUTH_USER_MODEL)), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(blank=True, db_index=True, default=django.utils.timezone.now)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("url", models.URLField(blank=True, max_length=1000, verbose_name="URL")), + ( + "creator", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="community_link_creator", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="community_link_modified", + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'verbose_name_plural': 'Links', - 'ordering': ['-created'], - 'verbose_name': 'Link', - 'get_latest_by': 'created', + "verbose_name_plural": "Links", + "ordering": ["-created"], + "verbose_name": "Link", + "get_latest_by": "created", }, ), migrations.CreateModel( - name='Photo', + name="Photo", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(blank=True, db_index=True, default=django.utils.timezone.now)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('image', models.ImageField(blank=True, upload_to='community/photos/')), - ('image_url', models.URLField(blank=True, max_length=1000, verbose_name='Image URL')), - ('caption', models.TextField(blank=True)), - ('click_through_url', models.URLField(blank=True)), - ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='community_photo_creator', to=settings.AUTH_USER_MODEL)), - ('last_modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='community_photo_modified', to=settings.AUTH_USER_MODEL)), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(blank=True, db_index=True, default=django.utils.timezone.now)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("image", models.ImageField(blank=True, upload_to="community/photos/")), + ("image_url", models.URLField(blank=True, max_length=1000, verbose_name="Image URL")), + ("caption", models.TextField(blank=True)), + ("click_through_url", models.URLField(blank=True)), + ( + "creator", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="community_photo_creator", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="community_photo_modified", + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'verbose_name_plural': 'photos', - 'ordering': ['-created'], - 'verbose_name': 'photo', - 'get_latest_by': 'created', + "verbose_name_plural": "photos", + "ordering": ["-created"], + "verbose_name": "photo", + "get_latest_by": "created", }, ), migrations.CreateModel( - name='Post', + name="Post", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(blank=True, db_index=True, default=django.utils.timezone.now)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('title', models.CharField(blank=True, max_length=200, null=True)), - ('content', markupfield.fields.MarkupField(rendered_field=True)), - ('abstract', models.TextField(blank=True, null=True)), - ('content_markup_type', models.CharField(choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], default='html', max_length=30)), - ('_content_rendered', models.TextField(editable=False)), - ('media_type', models.IntegerField(choices=[(1, 'text'), (2, 'photo'), (3, 'video'), (4, 'link')], default=1)), - ('source_url', models.URLField(blank=True, max_length=1000)), - ('meta', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default={})), - ('status', models.IntegerField(choices=[(1, 'private'), (2, 'public')], db_index=True, default=1)), - ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='community_post_creator', to=settings.AUTH_USER_MODEL)), - ('last_modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='community_post_modified', to=settings.AUTH_USER_MODEL)), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(blank=True, db_index=True, default=django.utils.timezone.now)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("title", models.CharField(blank=True, max_length=200, null=True)), + ("content", markupfield.fields.MarkupField(rendered_field=True)), + ("abstract", models.TextField(blank=True, null=True)), + ( + "content_markup_type", + models.CharField( + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + default="html", + max_length=30, + ), + ), + ("_content_rendered", models.TextField(editable=False)), + ( + "media_type", + models.IntegerField(choices=[(1, "text"), (2, "photo"), (3, "video"), (4, "link")], default=1), + ), + ("source_url", models.URLField(blank=True, max_length=1000)), + ("meta", django.contrib.postgres.fields.jsonb.JSONField(blank=True, default={})), + ("status", models.IntegerField(choices=[(1, "private"), (2, "public")], db_index=True, default=1)), + ( + "creator", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="community_post_creator", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="community_post_modified", + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'verbose_name_plural': 'posts', - 'ordering': ['-created'], - 'verbose_name': 'post', - 'get_latest_by': 'created', + "verbose_name_plural": "posts", + "ordering": ["-created"], + "verbose_name": "post", + "get_latest_by": "created", }, ), migrations.CreateModel( - name='Video', + name="Video", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(blank=True, db_index=True, default=django.utils.timezone.now)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('video_embed', models.TextField(blank=True)), - ('video_data', models.FileField(blank=True, upload_to='community/videos/')), - ('caption', models.TextField(blank=True)), - ('click_through_url', models.URLField(blank=True, verbose_name='Click Through URL')), - ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='community_video_creator', to=settings.AUTH_USER_MODEL)), - ('last_modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='community_video_modified', to=settings.AUTH_USER_MODEL)), - ('post', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='related_video', to='community.Post')), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(blank=True, db_index=True, default=django.utils.timezone.now)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("video_embed", models.TextField(blank=True)), + ("video_data", models.FileField(blank=True, upload_to="community/videos/")), + ("caption", models.TextField(blank=True)), + ("click_through_url", models.URLField(blank=True, verbose_name="Click Through URL")), + ( + "creator", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="community_video_creator", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="community_video_modified", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "post", + models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="related_video", + to="community.Post", + ), + ), ], options={ - 'verbose_name_plural': 'videos', - 'ordering': ['-created'], - 'verbose_name': 'video', - 'get_latest_by': 'created', + "verbose_name_plural": "videos", + "ordering": ["-created"], + "verbose_name": "video", + "get_latest_by": "created", }, ), migrations.AddField( - model_name='photo', - name='post', - field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='related_photo', to='community.Post'), + model_name="photo", + name="post", + field=models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="related_photo", + to="community.Post", + ), ), migrations.AddField( - model_name='link', - name='post', - field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='related_link', to='community.Post'), + model_name="link", + name="post", + field=models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="related_link", + to="community.Post", + ), ), migrations.AlterField( - model_name='post', - name='content_markup_type', - field=models.CharField(choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')], default='html', max_length=30), + model_name="post", + name="content_markup_type", + field=models.CharField( + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + default="html", + max_length=30, + ), ), migrations.AlterField( - model_name='post', - name='meta', + model_name="post", + name="meta", field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default={}), ), migrations.AlterField( - model_name='post', - name='meta', + model_name="post", + name="meta", field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict), ), ] diff --git a/community/migrations/0002_auto_20150416_1853.py b/community/migrations/0002_auto_20150416_1853.py index b306a7d55..2302b9630 100644 --- a/community/migrations/0002_auto_20150416_1853.py +++ b/community/migrations/0002_auto_20150416_1853.py @@ -2,16 +2,25 @@ class Migration(migrations.Migration): - dependencies = [ - ('community', '0001_initial'), + ("community", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='post', - name='content_markup_type', - field=models.CharField(max_length=30, default='html', choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')]), + model_name="post", + name="content_markup_type", + field=models.CharField( + max_length=30, + default="html", + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + ), preserve_default=True, ), ] diff --git a/community/migrations/0003_auto_20170831_0358.py b/community/migrations/0003_auto_20170831_0358.py index a0926679e..1ef0b44db 100644 --- a/community/migrations/0003_auto_20170831_0358.py +++ b/community/migrations/0003_auto_20170831_0358.py @@ -5,15 +5,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('community', '0002_auto_20150416_1853'), + ("community", "0002_auto_20150416_1853"), ] operations = [ migrations.AlterField( - model_name='post', - name='meta', + model_name="post", + name="meta", field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default={}), ), ] diff --git a/community/migrations/0004_auto_20170831_0541.py b/community/migrations/0004_auto_20170831_0541.py index 3c1cad60b..ddad476c5 100644 --- a/community/migrations/0004_auto_20170831_0541.py +++ b/community/migrations/0004_auto_20170831_0541.py @@ -5,15 +5,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('community', '0003_auto_20170831_0358'), + ("community", "0003_auto_20170831_0358"), ] operations = [ migrations.AlterField( - model_name='post', - name='meta', + model_name="post", + name="meta", field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict), ), ] diff --git a/community/migrations/0005_alter_link_creator_alter_link_last_modified_by_and_more.py b/community/migrations/0005_alter_link_creator_alter_link_last_modified_by_and_more.py index 9372dbf0e..085d74a82 100644 --- a/community/migrations/0005_alter_link_creator_alter_link_last_modified_by_and_more.py +++ b/community/migrations/0005_alter_link_creator_alter_link_last_modified_by_and_more.py @@ -6,71 +6,136 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('community', '0001_squashed_0004_auto_20170831_0541'), + ("community", "0001_squashed_0004_auto_20170831_0541"), ] operations = [ migrations.AlterField( - model_name='link', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="link", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='link', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="link", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='link', - name='post', - field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='related_%(class)s', to='community.post'), + model_name="link", + name="post", + field=models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="related_%(class)s", + to="community.post", + ), ), migrations.AlterField( - model_name='photo', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="photo", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='photo', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="photo", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='photo', - name='post', - field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='related_%(class)s', to='community.post'), + model_name="photo", + name="post", + field=models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="related_%(class)s", + to="community.post", + ), ), migrations.AlterField( - model_name='post', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="post", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='post', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="post", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='post', - name='meta', + model_name="post", + name="meta", field=models.JSONField(blank=True, default=dict), ), migrations.AlterField( - model_name='video', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="video", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='video', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="video", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='video', - name='post', - field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='related_%(class)s', to='community.post'), + model_name="video", + name="post", + field=models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="related_%(class)s", + to="community.post", + ), ), ] diff --git a/community/models.py b/community/models.py index 75ee94cd8..ce092266b 100644 --- a/community/models.py +++ b/community/models.py @@ -10,7 +10,7 @@ from .managers import PostQuerySet -DEFAULT_MARKUP_TYPE = 'html' +DEFAULT_MARKUP_TYPE = "html" class Post(ContentManageable): @@ -24,10 +24,10 @@ class Post(ContentManageable): MEDIA_LINK = 4 MEDIA_CHOICES = ( - (MEDIA_TEXT, 'text'), - (MEDIA_PHOTO, 'photo'), - (MEDIA_VIDEO, 'video'), - (MEDIA_LINK, 'link'), + (MEDIA_TEXT, "text"), + (MEDIA_PHOTO, "photo"), + (MEDIA_VIDEO, "video"), + (MEDIA_LINK, "link"), ) media_type = models.IntegerField(choices=MEDIA_CHOICES, default=MEDIA_TEXT) source_url = models.URLField(max_length=1000, blank=True) @@ -36,87 +36,90 @@ class Post(ContentManageable): STATUS_PRIVATE = 1 STATUS_PUBLIC = 2 STATUS_CHOICES = ( - (STATUS_PRIVATE, 'private'), - (STATUS_PUBLIC, 'public'), + (STATUS_PRIVATE, "private"), + (STATUS_PUBLIC, "public"), ) status = models.IntegerField(choices=STATUS_CHOICES, default=STATUS_PRIVATE, db_index=True) objects = PostQuerySet.as_manager() class Meta: - verbose_name = _('post') - verbose_name_plural = _('posts') - get_latest_by = 'created' - ordering = ['-created'] + verbose_name = _("post") + verbose_name_plural = _("posts") + get_latest_by = "created" + ordering = ["-created"] def __str__(self): - return f'Post {self.get_media_type_display()} ({self.pk})' + return f"Post {self.get_media_type_display()} ({self.pk})" def get_absolute_url(self): - return reverse('community:post_detail', kwargs={'pk': self.pk}) + return reverse("community:post_detail", kwargs={"pk": self.pk}) class Link(ContentManageable): post = models.ForeignKey( Post, - related_name='related_%(class)s', + related_name="related_%(class)s", editable=False, null=True, on_delete=models.CASCADE, ) - url = models.URLField('URL', max_length=1000, blank=True) + url = models.URLField("URL", max_length=1000, blank=True) class Meta: - verbose_name = _('Link') - verbose_name_plural = _('Links') - get_latest_by = 'created' - ordering = ['-created'] + verbose_name = _("Link") + verbose_name_plural = _("Links") + get_latest_by = "created" + ordering = ["-created"] def __str__(self): - return f'Link ({self.pk})' + return f"Link ({self.pk})" class Photo(ContentManageable): post = models.ForeignKey( Post, - related_name='related_%(class)s', + related_name="related_%(class)s", editable=False, null=True, on_delete=models.CASCADE, ) - image = models.ImageField(upload_to='community/photos/', blank=True) - image_url = models.URLField('Image URL', max_length=1000, blank=True) + image = models.ImageField(upload_to="community/photos/", blank=True) + image_url = models.URLField("Image URL", max_length=1000, blank=True) caption = models.TextField(blank=True) click_through_url = models.URLField(blank=True) class Meta: - verbose_name = _('photo') - verbose_name_plural = _('photos') - get_latest_by = 'created' - ordering = ['-created'] + verbose_name = _("photo") + verbose_name_plural = _("photos") + get_latest_by = "created" + ordering = ["-created"] def __str__(self): - return f'Photo ({self.pk})' + return f"Photo ({self.pk})" class Video(ContentManageable): post = models.ForeignKey( Post, - related_name='related_%(class)s', + related_name="related_%(class)s", editable=False, null=True, on_delete=models.CASCADE, ) video_embed = models.TextField(blank=True) - video_data = models.FileField(upload_to='community/videos/', blank=True, ) + video_data = models.FileField( + upload_to="community/videos/", + blank=True, + ) caption = models.TextField(blank=True) - click_through_url = models.URLField('Click Through URL', blank=True) + click_through_url = models.URLField("Click Through URL", blank=True) class Meta: - verbose_name = _('video') - verbose_name_plural = _('videos') - get_latest_by = 'created' - ordering = ['-created'] + verbose_name = _("video") + verbose_name_plural = _("videos") + get_latest_by = "created" + ordering = ["-created"] def __str__(self): - return f'Video ({self.pk})' + return f"Video ({self.pk})" diff --git a/community/templatetags/community.py b/community/templatetags/community.py index 30a02ed6a..9785126f2 100644 --- a/community/templatetags/community.py +++ b/community/templatetags/community.py @@ -27,7 +27,7 @@ def render_template_for(obj, template=None, template_directory=None): """ context = { - 'object': obj, + "object": obj, } template_list = [] @@ -38,11 +38,11 @@ def render_template_for(obj, template=None, template_directory=None): if template_directory: template_dirs.append(template_directory) - template_dirs.append('community/types') + template_dirs.append("community/types") for directory in template_dirs: - template_list.append(f'{directory}/{obj.get_media_type_display()}.html') - template_list.append(f'{directory}/default.html') + template_list.append(f"{directory}/{obj.get_media_type_display()}.html") + template_list.append(f"{directory}/default.html") output = render_to_string(template_list, context) return output diff --git a/community/tests/test_managers.py b/community/tests/test_managers.py index 8e91e5523..da1d53bdb 100644 --- a/community/tests/test_managers.py +++ b/community/tests/test_managers.py @@ -6,15 +6,9 @@ class CommunityManagersTest(TestCase): def test_post_manager(self): private_post = Post.objects.create( - content='private post', - media_type=Post.MEDIA_TEXT, - status=Post.STATUS_PRIVATE - ) - public_post = Post.objects.create( - content='public post', - media_type=Post.MEDIA_TEXT, - status=Post.STATUS_PUBLIC + content="private post", media_type=Post.MEDIA_TEXT, status=Post.STATUS_PRIVATE ) + public_post = Post.objects.create(content="public post", media_type=Post.MEDIA_TEXT, status=Post.STATUS_PUBLIC) self.assertQuerySetEqual(Post.objects.all(), [public_post, private_post], lambda x: x) self.assertQuerySetEqual(Post.objects.public(), [public_post], lambda x: x) diff --git a/community/tests/test_models.py b/community/tests/test_models.py index c3b929c47..551a64f56 100644 --- a/community/tests/test_models.py +++ b/community/tests/test_models.py @@ -4,14 +4,13 @@ class ModelTestCase(TestCase): - def test_json_field(self): post = Post.objects.create( - content='public post', + content="public post", media_type=Post.MEDIA_TEXT, status=Post.STATUS_PUBLIC, ) self.assertEqual(post.meta, {}) - post.meta = {'SPAM': 42} + post.meta = {"SPAM": 42} post.save() - self.assertEqual(post.meta, {'SPAM': 42}) + self.assertEqual(post.meta, {"SPAM": 42}) diff --git a/community/tests/test_views.py b/community/tests/test_views.py index bd6f4d859..ac84d4130 100644 --- a/community/tests/test_views.py +++ b/community/tests/test_views.py @@ -4,12 +4,8 @@ class CommunityTagsTest(TemplateTestCase): def test_render_template_for(self): - obj = Post.objects.create( - content='text post', - media_type=Post.MEDIA_TEXT, - status=Post.STATUS_PRIVATE - ) - template = '{% load community %}{% render_template_for post as html %}{{ html }}' - rendered = self.render_string(template, {'post': obj}) + obj = Post.objects.create(content="text post", media_type=Post.MEDIA_TEXT, status=Post.STATUS_PRIVATE) + template = "{% load community %}{% render_template_for post as html %}{{ html }}" + rendered = self.render_string(template, {"post": obj}) expected = '

todo: types/text.html - Post text ({0:d})

\n' self.assertEqual(rendered, expected.format(obj.pk)) diff --git a/community/urls.py b/community/urls.py index 531dfe015..1658e7c3a 100644 --- a/community/urls.py +++ b/community/urls.py @@ -1,8 +1,8 @@ from . import views from django.urls import path -app_name = 'community' +app_name = "community" urlpatterns = [ - path('', views.PostList.as_view(), name='post_list'), - path('/', views.PostDetail.as_view(), name='post_detail'), + path("", views.PostList.as_view(), name="post_list"), + path("/", views.PostDetail.as_view(), name="post_detail"), ] diff --git a/companies/admin.py b/companies/admin.py index f5e2da58f..2b9c0e6c8 100644 --- a/companies/admin.py +++ b/companies/admin.py @@ -7,6 +7,6 @@ @admin.register(Company) class CompanyAdmin(NameSlugAdmin): - search_fields = ['name'] - list_display = ['__str__', 'contact', 'email'] - ordering = ['-pk'] + search_fields = ["name"] + list_display = ["__str__", "contact", "email"] + ordering = ["-pk"] diff --git a/companies/apps.py b/companies/apps.py index d34262a3c..7bf99c83c 100644 --- a/companies/apps.py +++ b/companies/apps.py @@ -2,5 +2,4 @@ class CompaniesAppConfig(AppConfig): - - name = 'companies' + name = "companies" diff --git a/companies/factories.py b/companies/factories.py index 04e7b4ebf..5690b1e7d 100644 --- a/companies/factories.py +++ b/companies/factories.py @@ -5,18 +5,17 @@ class CompanyFactory(DjangoModelFactory): - class Meta: model = Company - django_get_or_create = ('name',) + django_get_or_create = ("name",) - name = factory.Faker('company') - contact = factory.Faker('name') - email = factory.Faker('company_email') - url = factory.Faker('url') + name = factory.Faker("company") + contact = factory.Faker("name") + email = factory.Faker("company_email") + url = factory.Faker("url") def initial_data(): return { - 'companies': CompanyFactory.create_batch(size=10), + "companies": CompanyFactory.create_batch(size=10), } diff --git a/companies/migrations/0001_initial.py b/companies/migrations/0001_initial.py index dff331e74..26fe94923 100644 --- a/companies/migrations/0001_initial.py +++ b/companies/migrations/0001_initial.py @@ -3,29 +3,41 @@ class Migration(migrations.Migration): - - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Company', + name="Company", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200)), - ('slug', models.SlugField(unique=True)), - ('about', markupfield.fields.MarkupField(rendered_field=True, blank=True)), - ('about_markup_type', models.CharField(max_length=30, choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], default='restructuredtext', blank=True)), - ('contact', models.CharField(max_length=100, blank=True, null=True)), - ('_about_rendered', models.TextField(editable=False)), - ('email', models.EmailField(max_length=75, blank=True, null=True)), - ('url', models.URLField(verbose_name='URL', blank=True, null=True)), - ('logo', models.ImageField(upload_to='companies/logos/', blank=True, null=True)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=200)), + ("slug", models.SlugField(unique=True)), + ("about", markupfield.fields.MarkupField(rendered_field=True, blank=True)), + ( + "about_markup_type", + models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + default="restructuredtext", + blank=True, + ), + ), + ("contact", models.CharField(max_length=100, blank=True, null=True)), + ("_about_rendered", models.TextField(editable=False)), + ("email", models.EmailField(max_length=75, blank=True, null=True)), + ("url", models.URLField(verbose_name="URL", blank=True, null=True)), + ("logo", models.ImageField(upload_to="companies/logos/", blank=True, null=True)), ], options={ - 'verbose_name': 'company', - 'verbose_name_plural': 'companies', - 'ordering': ('name',), + "verbose_name": "company", + "verbose_name_plural": "companies", + "ordering": ("name",), }, bases=(models.Model,), ), diff --git a/companies/migrations/0002_auto_20150416_1853.py b/companies/migrations/0002_auto_20150416_1853.py index f305695a9..e92f5059f 100644 --- a/companies/migrations/0002_auto_20150416_1853.py +++ b/companies/migrations/0002_auto_20150416_1853.py @@ -2,16 +2,26 @@ class Migration(migrations.Migration): - dependencies = [ - ('companies', '0001_initial'), + ("companies", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='company', - name='about_markup_type', - field=models.CharField(max_length=30, choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')], default='restructuredtext', blank=True), + model_name="company", + name="about_markup_type", + field=models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + default="restructuredtext", + blank=True, + ), preserve_default=True, ), ] diff --git a/companies/migrations/0003_auto_20170814_0301.py b/companies/migrations/0003_auto_20170814_0301.py index 7d901a6ea..0b0ac9236 100644 --- a/companies/migrations/0003_auto_20170814_0301.py +++ b/companies/migrations/0003_auto_20170814_0301.py @@ -2,15 +2,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('companies', '0002_auto_20150416_1853'), + ("companies", "0002_auto_20150416_1853"), ] operations = [ migrations.AlterField( - model_name='company', - name='email', + model_name="company", + name="email", field=models.EmailField(max_length=254, null=True, blank=True), ), ] diff --git a/companies/migrations/0004_auto_20170821_2000.py b/companies/migrations/0004_auto_20170821_2000.py index f1de3e8ed..4e7350d8a 100644 --- a/companies/migrations/0004_auto_20170821_2000.py +++ b/companies/migrations/0004_auto_20170821_2000.py @@ -2,15 +2,24 @@ class Migration(migrations.Migration): - dependencies = [ - ('companies', '0003_auto_20170814_0301'), + ("companies", "0003_auto_20170814_0301"), ] operations = [ migrations.AlterField( - model_name='company', - name='about_markup_type', - field=models.CharField(choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')], default='restructuredtext', max_length=30), + model_name="company", + name="about_markup_type", + field=models.CharField( + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + default="restructuredtext", + max_length=30, + ), ), ] diff --git a/companies/migrations/0005_auto_20180705_0352.py b/companies/migrations/0005_auto_20180705_0352.py index f2f7169b9..3000f5e94 100644 --- a/companies/migrations/0005_auto_20180705_0352.py +++ b/companies/migrations/0005_auto_20180705_0352.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('companies', '0004_auto_20170821_2000'), + ("companies", "0004_auto_20170821_2000"), ] operations = [ migrations.AlterField( - model_name='company', - name='slug', + model_name="company", + name="slug", field=models.SlugField(max_length=200, unique=True), ), ] diff --git a/companies/models.py b/companies/models.py index 0e97cc779..20fdd8e69 100644 --- a/companies/models.py +++ b/companies/models.py @@ -6,17 +6,17 @@ from cms.models import NameSlugModel -DEFAULT_MARKUP_TYPE = getattr(settings, 'DEFAULT_MARKUP_TYPE', 'restructuredtext') +DEFAULT_MARKUP_TYPE = getattr(settings, "DEFAULT_MARKUP_TYPE", "restructuredtext") class Company(NameSlugModel): about = MarkupField(blank=True, default_markup_type=DEFAULT_MARKUP_TYPE) contact = models.CharField(null=True, blank=True, max_length=100) email = models.EmailField(null=True, blank=True) - url = models.URLField('URL', null=True, blank=True) - logo = models.ImageField(upload_to='companies/logos/', blank=True, null=True) + url = models.URLField("URL", null=True, blank=True) + logo = models.ImageField(upload_to="companies/logos/", blank=True, null=True) class Meta: - verbose_name = _('company') - verbose_name_plural = _('companies') - ordering = ('name', ) + verbose_name = _("company") + verbose_name_plural = _("companies") + ordering = ("name",) diff --git a/companies/templatetags/companies.py b/companies/templatetags/companies.py index 14f7e1d30..9766ba37d 100644 --- a/companies/templatetags/companies.py +++ b/companies/templatetags/companies.py @@ -10,12 +10,12 @@ @stringfilter def render_email(value): if value: - mailbox, domain = value.split('@') - mailbox_tokens = mailbox.split('.') - domain_tokens = domain.split('.') + mailbox, domain = value.split("@") + mailbox_tokens = mailbox.split(".") + domain_tokens = domain.split(".") - mailbox = '.'.join(mailbox_tokens) - domain = '.'.join(domain_tokens) + mailbox = ".".join(mailbox_tokens) + domain = ".".join(domain_tokens) - return format_html('@'.join((mailbox, domain))) + return format_html("@".join((mailbox, domain))) return None diff --git a/companies/tests.py b/companies/tests.py index 083cd9dfe..a1b179ecf 100644 --- a/companies/tests.py +++ b/companies/tests.py @@ -1,10 +1,13 @@ from django.test import TestCase -from . import admin # coverage FTW +from . import admin # coverage FTW from .templatetags.companies import render_email class CompaniesTagsTests(TestCase): def test_render_email(self): - self.assertEqual(render_email(''), None) - self.assertEqual(render_email('firstname.lastname@domain.com'), 'firstname.lastname@domain.com') + self.assertEqual(render_email(""), None) + self.assertEqual( + render_email("firstname.lastname@domain.com"), + "firstname.lastname@domain.com", + ) diff --git a/custom_storages/storages.py b/custom_storages/storages.py index 567685603..58eb13c29 100644 --- a/custom_storages/storages.py +++ b/custom_storages/storages.py @@ -42,11 +42,7 @@ def get_comment_blocks(self, content): """ Return a list of (start, end) tuples for each comment block. """ - return [ - (match.start(), match.end()) - for match in re.finditer(r'\/\*.*?\*\/', content, flags=re.DOTALL) - ] - + return [(match.start(), match.end()) for match in re.finditer(r"\/\*.*?\*\/", content, flags=re.DOTALL)] def is_in_comment(self, pos, comments): for start, end in comments: @@ -56,7 +52,6 @@ def is_in_comment(self, pos, comments): return False return False - def url_converter(self, name, hashed_files, template=None, comment_blocks=[]): """ Return the custom URL converter for the given file name. @@ -112,9 +107,7 @@ def converter(matchobj): hashed_files=hashed_files, ) - transformed_url = "/".join( - url_path.split("/")[:-1] + hashed_url.split("/")[-1:] - ) + transformed_url = "/".join(url_path.split("/")[:-1] + hashed_url.split("/")[-1:]) # Restore the fragment that was stripped off earlier. if fragment: @@ -126,7 +119,6 @@ def converter(matchobj): return converter - def _post_process(self, paths, adjustable_paths, hashed_files): # Sort the files by directory level def path_level(name): @@ -163,9 +155,7 @@ def path_level(name): if matches_patterns(path, (extension,)): comment_blocks = self.get_comment_blocks(content) for pattern, template in patterns: - converter = self.url_converter( - name, hashed_files, template, comment_blocks - ) + converter = self.url_converter(name, hashed_files, template, comment_blocks) try: content = pattern.sub(converter, content) except ValueError as exc: diff --git a/dev-requirements.txt b/dev-requirements.txt index 8d61d0f9d..2fae56574 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -13,3 +13,5 @@ django-debug-toolbar==3.2.1 coverage ddt model-bakery==1.4.0 + +ruff diff --git a/docs/source/conf.py b/docs/source/conf.py index 00477aaa3..db6b7c124 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -5,67 +5,61 @@ import time extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.todo', - 'sphinx.ext.viewcode', - 'myst_parser', + "sphinx.ext.autodoc", + "sphinx.ext.todo", + "sphinx.ext.viewcode", + "myst_parser", ] -templates_path = ['_templates'] +templates_path = ["_templates"] -master_doc = 'index' +master_doc = "index" -project = 'Python.org Website' -copyright = '%s, Python Software Foundation' % time.strftime('%Y') +project = "Python.org Website" +copyright = "%s, Python Software Foundation" % time.strftime("%Y") # The short X.Y version. -version = '1.0' +version = "1.0" # The full version, including alpha/beta/rc tags. -release = '1.0' +release = "1.0" -html_title = 'Python.org Website' +html_title = "Python.org Website" -pygments_style = 'sphinx' +pygments_style = "sphinx" html_theme = "furo" -htmlhelp_basename = 'PythonorgWebsitedoc' +htmlhelp_basename = "PythonorgWebsitedoc" source_suffix = { - '.rst': 'restructuredtext', - '.md': 'markdown', + ".rst": "restructuredtext", + ".md": "markdown", } # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'PythonorgWebsite.tex', 'Python.org Website Documentation', - 'Python Software Foundation', 'manual'), + ("index", "PythonorgWebsite.tex", "Python.org Website Documentation", "Python Software Foundation", "manual"), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'pythonorgwebsite', 'Python.org Website Documentation', - ['Python Software Foundation'], 1) -] +man_pages = [("index", "pythonorgwebsite", "Python.org Website Documentation", ["Python Software Foundation"], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -73,7 +67,13 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'PythonorgWebsite', 'Python.org Website Documentation', - 'Python Software Foundation', 'PythonorgWebsite', '', - 'Miscellaneous'), + ( + "index", + "PythonorgWebsite", + "Python.org Website Documentation", + "Python Software Foundation", + "PythonorgWebsite", + "", + "Miscellaneous", + ), ] diff --git a/downloads/admin.py b/downloads/admin.py index d32f97b71..7920c05ac 100644 --- a/downloads/admin.py +++ b/downloads/admin.py @@ -19,9 +19,9 @@ class ReleaseFileInline(ContentManageableStackedInline): class ReleaseAdmin(ContentManageableModelAdmin): inlines = [ReleaseFileInline] prepopulated_fields = {"slug": ("name",)} - raw_id_fields = ['release_page'] - date_hierarchy = 'release_date' - list_display = ['__str__', 'is_published', 'show_on_download_page'] - list_filter = ['version', 'is_published', 'show_on_download_page'] - search_fields = ['name', 'slug'] - ordering = ['-release_date'] + raw_id_fields = ["release_page"] + date_hierarchy = "release_date" + list_display = ["__str__", "is_published", "show_on_download_page"] + list_filter = ["version", "is_published", "show_on_download_page"] + search_fields = ["name", "slug"] + ordering = ["-release_date"] diff --git a/downloads/api.py b/downloads/api.py index 73eb9b7bf..73b5a1766 100644 --- a/downloads/api.py +++ b/downloads/api.py @@ -17,78 +17,103 @@ class OSResource(GenericResource): class Meta(GenericResource.Meta): queryset = OS.objects.all() - resource_name = 'downloads/os' + resource_name = "downloads/os" fields = [ - 'name', 'slug', + "name", + "slug", # The following fields won't show up in the response # because there is no 'User' relation defined in the API. # See 'ReleaseResource.release_page' for an example. - 'creator', 'last_modified_by' + "creator", + "last_modified_by", ] filtering = { - 'name': ('exact',), - 'slug': ('exact',), + "name": ("exact",), + "slug": ("exact",), } abstract = False class ReleaseResource(GenericResource): - release_page = fields.ToOneField(PageResource, 'release_page', null=True, blank=True) + release_page = fields.ToOneField(PageResource, "release_page", null=True, blank=True) class Meta(GenericResource.Meta): queryset = Release.objects.all() - resource_name = 'downloads/release' + resource_name = "downloads/release" authorization = OnlyPublishedAuthorization() fields = [ - 'name', 'slug', - 'creator', 'last_modified_by', - 'version', 'is_published', 'release_date', 'pre_release', - 'release_page', 'release_notes_url', 'show_on_download_page', - 'is_latest', + "name", + "slug", + "creator", + "last_modified_by", + "version", + "is_published", + "release_date", + "pre_release", + "release_page", + "release_notes_url", + "show_on_download_page", + "is_latest", ] filtering = { - 'name': ('exact',), - 'slug': ('exact',), - 'is_published': ('exact',), - 'pre_release': ('exact',), - 'version': ('exact', 'startswith',), - 'release_date': (ALL,) + "name": ("exact",), + "slug": ("exact",), + "is_published": ("exact",), + "pre_release": ("exact",), + "version": ( + "exact", + "startswith", + ), + "release_date": (ALL,), } abstract = False class ReleaseFileResource(GenericResource): - os = fields.ToOneField(OSResource, 'os') - release = fields.ToOneField(ReleaseResource, 'release') + os = fields.ToOneField(OSResource, "os") + release = fields.ToOneField(ReleaseResource, "release") class Meta(GenericResource.Meta): queryset = ReleaseFile.objects.all() - resource_name = 'downloads/release_file' + resource_name = "downloads/release_file" fields = [ - 'name', 'slug', - 'creator', 'last_modified_by', - 'os', 'release', 'description', 'is_source', 'url', 'gpg_signature_file', - 'md5_sum', 'filesize', 'download_button', 'sigstore_signature_file', - 'sigstore_cert_file', 'sigstore_bundle_file', 'sbom_spdx2_file', + "name", + "slug", + "creator", + "last_modified_by", + "os", + "release", + "description", + "is_source", + "url", + "gpg_signature_file", + "md5_sum", + "filesize", + "download_button", + "sigstore_signature_file", + "sigstore_cert_file", + "sigstore_bundle_file", + "sbom_spdx2_file", ] filtering = { - 'name': ('exact',), - 'slug': ('exact',), - 'os': ALL_WITH_RELATIONS, - 'release': ALL_WITH_RELATIONS, - 'description': ('contains',), + "name": ("exact",), + "slug": ("exact",), + "os": ALL_WITH_RELATIONS, + "release": ALL_WITH_RELATIONS, + "description": ("contains",), } abstract = False # Django Rest Framework + class OSViewSet(viewsets.ModelViewSet): queryset = OS.objects.all() serializer_class = OSSerializer authentication_classes = (TokenAuthentication,) permission_classes = (IsStaffOrReadOnly,) - filterset_fields = ('name', 'slug') + filterset_fields = ("name", "slug") class ReleaseViewSet(BaseAPIViewSet): @@ -97,25 +122,24 @@ class ReleaseViewSet(BaseAPIViewSet): authentication_classes = (TokenAuthentication,) permission_classes = (IsStaffOrReadOnly,) filterset_fields = ( - 'name', - 'slug', - 'is_published', - 'pre_release', - 'version', - 'release_date', + "name", + "slug", + "is_published", + "pre_release", + "version", + "release_date", ) class ReleaseFileFilter(BaseFilterSet): - class Meta: model = ReleaseFile fields = { - 'name': ['exact'], - 'slug': ['exact'], - 'description': ['contains'], - 'os': ['exact'], - 'release': ['exact'], + "name": ["exact"], + "slug": ["exact"], + "description": ["contains"], + "os": ["exact"], + "release": ["exact"], } @@ -126,9 +150,9 @@ class ReleaseFileViewSet(viewsets.ModelViewSet): permission_classes = (IsStaffOrReadOnly,) filterset_class = ReleaseFileFilter - @action(detail=False, methods=['delete']) + @action(detail=False, methods=["delete"]) def delete_by_release(self, request): - release = request.query_params.get('release') + release = request.query_params.get("release") if release is None: return Response(status=status.HTTP_400_BAD_REQUEST) # TODO: We can add support for pagination in the future. diff --git a/downloads/apps.py b/downloads/apps.py index 18c2db44c..e45506db7 100644 --- a/downloads/apps.py +++ b/downloads/apps.py @@ -2,5 +2,4 @@ class DownloadsAppConfig(AppConfig): - - name = 'downloads' + name = "downloads" diff --git a/downloads/factories.py b/downloads/factories.py index 4ebcbdc22..91ef3456a 100644 --- a/downloads/factories.py +++ b/downloads/factories.py @@ -10,29 +10,26 @@ class OSFactory(DjangoModelFactory): - class Meta: model = OS - django_get_or_create = ('slug',) + django_get_or_create = ("slug",) creator = factory.SubFactory(UserFactory) class ReleaseFactory(DjangoModelFactory): - class Meta: model = Release - django_get_or_create = ('slug',) + django_get_or_create = ("slug",) creator = factory.SubFactory(UserFactory) is_published = True class ReleaseFileFactory(DjangoModelFactory): - class Meta: model = ReleaseFile - django_get_or_create = ('slug',) + django_get_or_create = ("slug",) creator = factory.SubFactory(UserFactory) release = factory.SubFactory(ReleaseFactory) @@ -40,17 +37,14 @@ class Meta: class APISession(requests.Session): - base_url = 'https://www.python.org/api/v2/' + base_url = "https://www.python.org/api/v2/" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.headers.update( { - 'Accept': 'application/json', - 'User-Agent': ( - f'pythondotorg/create_initial_data' - f' ({requests.utils.default_user_agent()})' - ), + "Accept": "application/json", + "User-Agent": (f"pythondotorg/create_initial_data" f" ({requests.utils.default_user_agent()})"), } ) @@ -65,10 +59,10 @@ def _get_id(obj, key): """ Get the ID of an object by extracting it from the resource_uri field. """ - resource_uri = obj.pop(key, '') + resource_uri = obj.pop(key, "") if resource_uri: # i.e. /foo/1/ -> /foo/1 -> ('/foo', '/', '1') -> '1' - return resource_uri.rstrip('/').rpartition('/')[-1] + return resource_uri.rstrip("/").rpartition("/")[-1] def initial_data(): @@ -77,48 +71,48 @@ def initial_data(): it from the python.org API. """ objects = { - 'oses': {}, - 'releases': {}, - 'release_files': {}, + "oses": {}, + "releases": {}, + "release_files": {}, } with APISession() as session: for key, resource_uri in [ - ('oses', 'downloads/os/'), - ('releases', 'downloads/release/'), - ('release_files', 'downloads/release_file/'), + ("oses", "downloads/os/"), + ("releases", "downloads/release/"), + ("release_files", "downloads/release_file/"), ]: response = session.get(resource_uri) object_list = response.json() for obj in object_list: - objects[key][_get_id(obj, 'resource_uri')] = obj + objects[key][_get_id(obj, "resource_uri")] = obj # Create the list of operating systems - objects['oses'] = {k: OSFactory(**obj) for k, obj in objects['oses'].items()} + objects["oses"] = {k: OSFactory(**obj) for k, obj in objects["oses"].items()} # Create all the releases - for key, obj in objects['releases'].items(): + for key, obj in objects["releases"].items(): # TODO: We are ignoring release pages for now. - obj.pop('release_page') - objects['releases'][key] = ReleaseFactory(**obj) + obj.pop("release_page") + objects["releases"][key] = ReleaseFactory(**obj) # Create all release files. - for key, obj in tuple(objects['release_files'].items()): - release_id = _get_id(obj, 'release') + for key, obj in tuple(objects["release_files"].items()): + release_id = _get_id(obj, "release") try: - release = objects['releases'][release_id] + release = objects["releases"][release_id] except KeyError: # Release files for draft releases are available through the API, # the releases are not. See #1308 for details. - objects['release_files'].pop(key) + objects["release_files"].pop(key) else: - obj['release'] = release - obj['os'] = objects['oses'][_get_id(obj, 'os')] - objects['release_files'][key] = ReleaseFileFactory(**obj) + obj["release"] = release + obj["os"] = objects["oses"][_get_id(obj, "os")] + objects["release_files"][key] = ReleaseFileFactory(**obj) return { - 'oses': list(objects.pop('oses').values()), - 'releases': list(objects.pop('releases').values()), - 'release_files': list(objects.pop('release_files').values()), + "oses": list(objects.pop("oses").values()), + "releases": list(objects.pop("releases").values()), + "release_files": list(objects.pop("release_files").values()), } diff --git a/downloads/managers.py b/downloads/managers.py index b529dcdd4..ea3880933 100644 --- a/downloads/managers.py +++ b/downloads/managers.py @@ -10,12 +10,16 @@ def draft(self): return self.filter(is_published=False) def downloads(self): - """ For the main downloads landing page """ - return self.select_related('release_page').filter( - is_published=True, - show_on_download_page=True, - pre_release=False, - ).order_by('-release_date') + """For the main downloads landing page""" + return ( + self.select_related("release_page") + .filter( + is_published=True, + show_on_download_page=True, + pre_release=False, + ) + .order_by("-release_date") + ) def python2(self): return self.filter(version=2, is_published=True) diff --git a/downloads/migrations/0001_initial.py b/downloads/migrations/0001_initial.py index e306ea22c..6fb6c5adc 100644 --- a/downloads/migrations/0001_initial.py +++ b/downloads/migrations/0001_initial.py @@ -5,85 +5,203 @@ class Migration(migrations.Migration): - dependencies = [ - ('pages', '0001_initial'), + ("pages", "0001_initial"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='OS', + name="OS", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('name', models.CharField(max_length=200)), - ('slug', models.SlugField(unique=True)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='downloads_os_creator', blank=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='downloads_os_modified', blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("name", models.CharField(max_length=200)), + ("slug", models.SlugField(unique=True)), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="downloads_os_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="downloads_os_modified", + blank=True, + on_delete=models.CASCADE, + ), + ), ], options={ - 'verbose_name': 'Operating System', - 'verbose_name_plural': 'Operating Systems', - 'ordering': ('name',), + "verbose_name": "Operating System", + "verbose_name_plural": "Operating Systems", + "ordering": ("name",), }, bases=(models.Model,), ), migrations.CreateModel( - name='Release', + name="Release", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('name', models.CharField(max_length=200)), - ('slug', models.SlugField(unique=True)), - ('version', models.IntegerField(choices=[(3, 'Python 3.x.x'), (2, 'Python 2.x.x'), (1, 'Python 1.x.x')], default=2)), - ('is_latest', models.BooleanField(help_text="Set this if this should be considered the latest release for the major version. Previous 'latest' versions will automatically have this flag turned off.", db_index=True, verbose_name='Is this the latest release?', default=False)), - ('is_published', models.BooleanField(help_text='Whether or not this should be considered a released/published version', db_index=True, verbose_name='Is Published?', default=False)), - ('pre_release', models.BooleanField(help_text='Boolean to denote pre-release/beta/RC versions', db_index=True, verbose_name='Pre-release', default=False)), - ('show_on_download_page', models.BooleanField(help_text='Whether or not to show this release on the main /downloads/ page', db_index=True, default=True)), - ('release_date', models.DateTimeField(default=django.utils.timezone.now)), - ('release_notes_url', models.URLField(verbose_name='Release Notes URL', blank=True)), - ('content', markupfield.fields.MarkupField(default='', rendered_field=True)), - ('content_markup_type', models.CharField(max_length=30, choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], default='restructuredtext')), - ('_content_rendered', models.TextField(editable=False)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='downloads_release_creator', blank=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='downloads_release_modified', blank=True, on_delete=models.CASCADE)), - ('release_page', models.ForeignKey(null=True, to='pages.Page', related_name='release', blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("name", models.CharField(max_length=200)), + ("slug", models.SlugField(unique=True)), + ( + "version", + models.IntegerField( + choices=[(3, "Python 3.x.x"), (2, "Python 2.x.x"), (1, "Python 1.x.x")], default=2 + ), + ), + ( + "is_latest", + models.BooleanField( + help_text="Set this if this should be considered the latest release for the major version. Previous 'latest' versions will automatically have this flag turned off.", + db_index=True, + verbose_name="Is this the latest release?", + default=False, + ), + ), + ( + "is_published", + models.BooleanField( + help_text="Whether or not this should be considered a released/published version", + db_index=True, + verbose_name="Is Published?", + default=False, + ), + ), + ( + "pre_release", + models.BooleanField( + help_text="Boolean to denote pre-release/beta/RC versions", + db_index=True, + verbose_name="Pre-release", + default=False, + ), + ), + ( + "show_on_download_page", + models.BooleanField( + help_text="Whether or not to show this release on the main /downloads/ page", + db_index=True, + default=True, + ), + ), + ("release_date", models.DateTimeField(default=django.utils.timezone.now)), + ("release_notes_url", models.URLField(verbose_name="Release Notes URL", blank=True)), + ("content", markupfield.fields.MarkupField(default="", rendered_field=True)), + ( + "content_markup_type", + models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + default="restructuredtext", + ), + ), + ("_content_rendered", models.TextField(editable=False)), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="downloads_release_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="downloads_release_modified", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "release_page", + models.ForeignKey( + null=True, to="pages.Page", related_name="release", blank=True, on_delete=models.CASCADE + ), + ), ], options={ - 'verbose_name': 'Release', - 'verbose_name_plural': 'Releases', - 'ordering': ('name',), - 'get_latest_by': 'release_date', + "verbose_name": "Release", + "verbose_name_plural": "Releases", + "ordering": ("name",), + "get_latest_by": "release_date", }, bases=(models.Model,), ), migrations.CreateModel( - name='ReleaseFile', + name="ReleaseFile", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('name', models.CharField(max_length=200)), - ('slug', models.SlugField(unique=True)), - ('description', models.TextField(blank=True)), - ('is_source', models.BooleanField(verbose_name='Is Source Distribution', default=False)), - ('url', models.URLField(help_text='Download URL', verbose_name='URL', unique=True, db_index=True)), - ('gpg_signature_file', models.URLField(help_text='GPG Signature URL', verbose_name='GPG SIG URL', blank=True)), - ('md5_sum', models.CharField(max_length=200, verbose_name='MD5 Sum', blank=True)), - ('filesize', models.IntegerField(default=0)), - ('download_button', models.BooleanField(help_text='Use for the supernav download button for this OS', default=False)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='downloads_releasefile_creator', blank=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='downloads_releasefile_modified', blank=True, on_delete=models.CASCADE)), - ('os', models.ForeignKey(verbose_name='OS', to='downloads.OS', related_name='releases', on_delete=models.CASCADE)), - ('release', models.ForeignKey(to='downloads.Release', related_name='files', on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("name", models.CharField(max_length=200)), + ("slug", models.SlugField(unique=True)), + ("description", models.TextField(blank=True)), + ("is_source", models.BooleanField(verbose_name="Is Source Distribution", default=False)), + ("url", models.URLField(help_text="Download URL", verbose_name="URL", unique=True, db_index=True)), + ( + "gpg_signature_file", + models.URLField(help_text="GPG Signature URL", verbose_name="GPG SIG URL", blank=True), + ), + ("md5_sum", models.CharField(max_length=200, verbose_name="MD5 Sum", blank=True)), + ("filesize", models.IntegerField(default=0)), + ( + "download_button", + models.BooleanField(help_text="Use for the supernav download button for this OS", default=False), + ), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="downloads_releasefile_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="downloads_releasefile_modified", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "os", + models.ForeignKey( + verbose_name="OS", to="downloads.OS", related_name="releases", on_delete=models.CASCADE + ), + ), + ("release", models.ForeignKey(to="downloads.Release", related_name="files", on_delete=models.CASCADE)), ], options={ - 'verbose_name': 'Release File', - 'verbose_name_plural': 'Release Files', - 'ordering': ('-release__is_published', 'release__name', 'os__name', 'name'), + "verbose_name": "Release File", + "verbose_name_plural": "Release Files", + "ordering": ("-release__is_published", "release__name", "os__name", "name"), }, bases=(models.Model,), ), diff --git a/downloads/migrations/0002_auto_20150416_1853.py b/downloads/migrations/0002_auto_20150416_1853.py index 560ac3bd3..3cfbad240 100644 --- a/downloads/migrations/0002_auto_20150416_1853.py +++ b/downloads/migrations/0002_auto_20150416_1853.py @@ -2,16 +2,25 @@ class Migration(migrations.Migration): - dependencies = [ - ('downloads', '0001_initial'), + ("downloads", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='release', - name='content_markup_type', - field=models.CharField(max_length=30, default='restructuredtext', choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')]), + model_name="release", + name="content_markup_type", + field=models.CharField( + max_length=30, + default="restructuredtext", + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + ), preserve_default=True, ), ] diff --git a/downloads/migrations/0003_auto_20150824_1612.py b/downloads/migrations/0003_auto_20150824_1612.py index 869bbd7a7..f8ac8b104 100644 --- a/downloads/migrations/0003_auto_20150824_1612.py +++ b/downloads/migrations/0003_auto_20150824_1612.py @@ -2,16 +2,17 @@ class Migration(migrations.Migration): - dependencies = [ - ('downloads', '0002_auto_20150416_1853'), + ("downloads", "0002_auto_20150416_1853"), ] operations = [ migrations.AlterField( - model_name='release', - name='version', - field=models.IntegerField(default=3, choices=[(3, 'Python 3.x.x'), (2, 'Python 2.x.x'), (1, 'Python 1.x.x')]), + model_name="release", + name="version", + field=models.IntegerField( + default=3, choices=[(3, "Python 3.x.x"), (2, "Python 2.x.x"), (1, "Python 1.x.x")] + ), preserve_default=True, ), ] diff --git a/downloads/migrations/0004_auto_20170821_2000.py b/downloads/migrations/0004_auto_20170821_2000.py index b68cd8a5a..96b9d09b5 100644 --- a/downloads/migrations/0004_auto_20170821_2000.py +++ b/downloads/migrations/0004_auto_20170821_2000.py @@ -2,15 +2,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('downloads', '0003_auto_20150824_1612'), + ("downloads", "0003_auto_20150824_1612"), ] operations = [ migrations.AlterField( - model_name='release', - name='_content_rendered', - field=models.TextField(editable=False, default=''), + model_name="release", + name="_content_rendered", + field=models.TextField(editable=False, default=""), ), ] diff --git a/downloads/migrations/0005_move_release_page_content.py b/downloads/migrations/0005_move_release_page_content.py index fabd38ea1..2049082e8 100644 --- a/downloads/migrations/0005_move_release_page_content.py +++ b/downloads/migrations/0005_move_release_page_content.py @@ -2,25 +2,25 @@ from django.db import migrations -MARKER = '.. Migrated from Release.release_page field.\n\n' +MARKER = ".. Migrated from Release.release_page field.\n\n" def migrate_old_content(apps, schema_editor): - Release = apps.get_model('downloads', 'Release') + Release = apps.get_model("downloads", "Release") db_alias = schema_editor.connection.alias releases = Release.objects.using(db_alias).filter( release_page__isnull=False, ) for release in releases: - content = '\n'.join(release.release_page.content.raw.splitlines()[3:]) + content = "\n".join(release.release_page.content.raw.splitlines()[3:]) release.content = MARKER + content release.release_page = None release.save() def delete_migrated_content(apps, schema_editor): - Release = apps.get_model('downloads', 'Release') - Page = apps.get_model('pages', 'Page') + Release = apps.get_model("downloads", "Release") + Page = apps.get_model("pages", "Page") db_alias = schema_editor.connection.alias releases = Release.objects.using(db_alias).filter( release_page__isnull=True, @@ -29,21 +29,20 @@ def delete_migrated_content(apps, schema_editor): for release in releases: try: name = release.name - if 'Release' not in name: - name = release.name + ' Release' + if "Release" not in name: + name = release.name + " Release" page = Page.objects.get(title=name) except (Page.DoesNotExist, Page.MultipleObjectsReturned): continue else: release.release_page = page - release.content = '' + release.content = "" release.save() class Migration(migrations.Migration): - dependencies = [ - ('downloads', '0004_auto_20170821_2000'), + ("downloads", "0004_auto_20170821_2000"), ] operations = [ diff --git a/downloads/migrations/0006_auto_20180705_0352.py b/downloads/migrations/0006_auto_20180705_0352.py index 5d438ecbf..7678baaeb 100644 --- a/downloads/migrations/0006_auto_20180705_0352.py +++ b/downloads/migrations/0006_auto_20180705_0352.py @@ -4,25 +4,24 @@ class Migration(migrations.Migration): - dependencies = [ - ('downloads', '0005_move_release_page_content'), + ("downloads", "0005_move_release_page_content"), ] operations = [ migrations.AlterField( - model_name='os', - name='slug', + model_name="os", + name="slug", field=models.SlugField(max_length=200, unique=True), ), migrations.AlterField( - model_name='release', - name='slug', + model_name="release", + name="slug", field=models.SlugField(max_length=200, unique=True), ), migrations.AlterField( - model_name='releasefile', - name='slug', + model_name="releasefile", + name="slug", field=models.SlugField(max_length=200, unique=True), ), ] diff --git a/downloads/migrations/0007_auto_20220809_1655.py b/downloads/migrations/0007_auto_20220809_1655.py index 615ad67a1..90f7d5110 100644 --- a/downloads/migrations/0007_auto_20220809_1655.py +++ b/downloads/migrations/0007_auto_20220809_1655.py @@ -4,20 +4,21 @@ class Migration(migrations.Migration): - dependencies = [ - ('downloads', '0006_auto_20180705_0352'), + ("downloads", "0006_auto_20180705_0352"), ] operations = [ migrations.AddField( - model_name='releasefile', - name='sigstore_cert_file', - field=models.URLField(blank=True, help_text='Sigstore Cert URL', verbose_name='Sigstore Cert URL'), + model_name="releasefile", + name="sigstore_cert_file", + field=models.URLField(blank=True, help_text="Sigstore Cert URL", verbose_name="Sigstore Cert URL"), ), migrations.AddField( - model_name='releasefile', - name='sigstore_signature_file', - field=models.URLField(blank=True, help_text='Sigstore Signature URL', verbose_name='Sigstore Signature URL'), + model_name="releasefile", + name="sigstore_signature_file", + field=models.URLField( + blank=True, help_text="Sigstore Signature URL", verbose_name="Sigstore Signature URL" + ), ), ] diff --git a/downloads/migrations/0008_auto_20220907_2102.py b/downloads/migrations/0008_auto_20220907_2102.py index 81f6d5ca5..a30ffe698 100644 --- a/downloads/migrations/0008_auto_20220907_2102.py +++ b/downloads/migrations/0008_auto_20220907_2102.py @@ -4,14 +4,17 @@ class Migration(migrations.Migration): - dependencies = [ - ('downloads', '0007_auto_20220809_1655'), + ("downloads", "0007_auto_20220809_1655"), ] operations = [ migrations.AddConstraint( - model_name='releasefile', - constraint=models.UniqueConstraint(condition=models.Q(download_button=True), fields=('os', 'release'), name='only_one_download_per_os_per_release'), + model_name="releasefile", + constraint=models.UniqueConstraint( + condition=models.Q(download_button=True), + fields=("os", "release"), + name="only_one_download_per_os_per_release", + ), ), ] diff --git a/downloads/migrations/0009_releasefile_sigstore_bundle_file.py b/downloads/migrations/0009_releasefile_sigstore_bundle_file.py index 52383852c..7e707e2de 100644 --- a/downloads/migrations/0009_releasefile_sigstore_bundle_file.py +++ b/downloads/migrations/0009_releasefile_sigstore_bundle_file.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('downloads', '0008_auto_20220907_2102'), + ("downloads", "0008_auto_20220907_2102"), ] operations = [ migrations.AddField( - model_name='releasefile', - name='sigstore_bundle_file', - field=models.URLField(blank=True, help_text='Sigstore Bundle URL', verbose_name='Sigstore Bundle URL'), + model_name="releasefile", + name="sigstore_bundle_file", + field=models.URLField(blank=True, help_text="Sigstore Bundle URL", verbose_name="Sigstore Bundle URL"), ), ] diff --git a/downloads/migrations/0010_releasefile_sbom_spdx2_file.py b/downloads/migrations/0010_releasefile_sbom_spdx2_file.py index f3a4784e9..c267fe8c5 100644 --- a/downloads/migrations/0010_releasefile_sbom_spdx2_file.py +++ b/downloads/migrations/0010_releasefile_sbom_spdx2_file.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('downloads', '0009_releasefile_sigstore_bundle_file'), + ("downloads", "0009_releasefile_sigstore_bundle_file"), ] operations = [ migrations.AddField( - model_name='releasefile', - name='sbom_spdx2_file', - field=models.URLField(blank=True, help_text='SPDX-2 SBOM URL', verbose_name='SPDX-2 SBOM URL'), + model_name="releasefile", + name="sbom_spdx2_file", + field=models.URLField(blank=True, help_text="SPDX-2 SBOM URL", verbose_name="SPDX-2 SBOM URL"), ), ] diff --git a/downloads/migrations/0011_alter_os_creator_alter_os_last_modified_by_and_more.py b/downloads/migrations/0011_alter_os_creator_alter_os_last_modified_by_and_more.py index 368d575c2..690e75288 100644 --- a/downloads/migrations/0011_alter_os_creator_alter_os_last_modified_by_and_more.py +++ b/downloads/migrations/0011_alter_os_creator_alter_os_last_modified_by_and_more.py @@ -6,41 +6,76 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('downloads', '0010_releasefile_sbom_spdx2_file'), + ("downloads", "0010_releasefile_sbom_spdx2_file"), ] operations = [ migrations.AlterField( - model_name='os', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="os", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='os', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="os", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='release', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="release", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='release', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="release", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='releasefile', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="releasefile", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='releasefile', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="releasefile", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/downloads/models.py b/downloads/models.py index 4a9c5781c..35955c635 100644 --- a/downloads/models.py +++ b/downloads/models.py @@ -19,22 +19,22 @@ from .managers import ReleaseManager -DEFAULT_MARKUP_TYPE = getattr(settings, 'DEFAULT_MARKUP_TYPE', 'restructuredtext') +DEFAULT_MARKUP_TYPE = getattr(settings, "DEFAULT_MARKUP_TYPE", "restructuredtext") class OS(ContentManageable, NameSlugModel): - """ OS for Python release """ + """OS for Python release""" class Meta: - verbose_name = 'Operating System' - verbose_name_plural = 'Operating Systems' - ordering = ('name', ) + verbose_name = "Operating System" + verbose_name_plural = "Operating Systems" + ordering = ("name",) def __str__(self): return self.name def get_absolute_url(self): - return reverse('download:download_os_list', kwargs={'os_slug': self.slug}) + return reverse("download:download_os_list", kwargs={"os_slug": self.slug}) class Release(ContentManageable, NameSlugModel): @@ -42,31 +42,32 @@ class Release(ContentManageable, NameSlugModel): A particular version release. Name field should be version number for example: 3.3.4 or 2.7.6 """ + PYTHON1 = 1 PYTHON2 = 2 PYTHON3 = 3 PYTHON_VERSION_CHOICES = ( - (PYTHON3, 'Python 3.x.x'), - (PYTHON2, 'Python 2.x.x'), - (PYTHON1, 'Python 1.x.x'), + (PYTHON3, "Python 3.x.x"), + (PYTHON2, "Python 2.x.x"), + (PYTHON1, "Python 1.x.x"), ) version = models.IntegerField(default=PYTHON3, choices=PYTHON_VERSION_CHOICES) is_latest = models.BooleanField( - verbose_name='Is this the latest release?', + verbose_name="Is this the latest release?", default=False, db_index=True, help_text="Set this if this should be considered the latest release " - "for the major version. Previous 'latest' versions will " - "automatically have this flag turned off.", + "for the major version. Previous 'latest' versions will " + "automatically have this flag turned off.", ) is_published = models.BooleanField( - verbose_name='Is Published?', + verbose_name="Is Published?", default=False, db_index=True, help_text="Whether or not this should be considered a released/published version", ) pre_release = models.BooleanField( - verbose_name='Pre-release', + verbose_name="Pre-release", default=False, db_index=True, help_text="Boolean to denote pre-release/beta/RC versions", @@ -79,22 +80,22 @@ class Release(ContentManageable, NameSlugModel): release_date = models.DateTimeField(default=timezone.now) release_page = models.ForeignKey( Page, - related_name='release', + related_name="release", blank=True, null=True, on_delete=models.CASCADE, ) - release_notes_url = models.URLField('Release Notes URL', blank=True) + release_notes_url = models.URLField("Release Notes URL", blank=True) - content = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE, default='') + content = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE, default="") objects = ReleaseManager() class Meta: - verbose_name = 'Release' - verbose_name_plural = 'Releases' - ordering = ('name', ) - get_latest_by = 'release_date' + verbose_name = "Release" + verbose_name_plural = "Releases" + ordering = ("name",) + get_latest_by = "release_date" def __str__(self): return self.name @@ -103,10 +104,10 @@ def get_absolute_url(self): if not self.content.raw and self.release_page: return self.release_page.get_absolute_url() else: - return reverse('download:download_release_detail', kwargs={'release_slug': self.slug}) + return reverse("download:download_release_detail", kwargs={"release_slug": self.slug}) def download_file_for_os(self, os_slug): - """ Given an OS slug return the appropriate download file """ + """Given an OS slug return the appropriate download file""" try: file = self.files.get(os__slug=os_slug, download_button=True) except ReleaseFile.DoesNotExist: @@ -115,19 +116,19 @@ def download_file_for_os(self, os_slug): return file def files_for_os(self, os_slug): - """ Return all files for this release for a given OS """ - files = self.files.filter(os__slug=os_slug).order_by('-name') + """Return all files for this release for a given OS""" + files = self.files.filter(os__slug=os_slug).order_by("-name") return files def get_version(self): - version = re.match(r'Python\s([\d.]+)', self.name) + version = re.match(r"Python\s([\d.]+)", self.name) if version is not None: return version.group(1) return None def is_version_at_least(self, min_version_tuple): v1 = [] - for b in self.get_version().split('.'): + for b in self.get_version().split("."): try: v1.append(int(b)) except ValueError: @@ -155,36 +156,39 @@ def update_supernav(): python_files = [] for o in OS.objects.all(): data = { - 'os': o, - 'python3': None, + "os": o, + "python3": None, } release_file = latest_python3.download_file_for_os(o.slug) if not release_file: continue - data['python3'] = release_file + data["python3"] = release_file python_files.append(data) if not python_files: return - if not all(f['python3'] for f in python_files): + if not all(f["python3"] for f in python_files): # We have a latest Python release, different OSes, but don't have release # files for the release, so return early. return - content = render_to_string('downloads/supernav.html', { - 'python_files': python_files, - 'last_updated': timezone.now(), - }) + content = render_to_string( + "downloads/supernav.html", + { + "python_files": python_files, + "last_updated": timezone.now(), + }, + ) box, _ = Box.objects.update_or_create( - label='supernav-python-downloads', + label="supernav-python-downloads", defaults={ - 'content': content, - 'content_markup_type': 'html', - } + "content": content, + "content_markup_type": "html", + }, ) @@ -195,25 +199,25 @@ def update_download_landing_sources_box(): context = {} if latest_python2: - latest_python2_source = latest_python2.download_file_for_os('source') + latest_python2_source = latest_python2.download_file_for_os("source") if latest_python2_source: - context['latest_python2_source'] = latest_python2_source + context["latest_python2_source"] = latest_python2_source if latest_python3: - latest_python3_source = latest_python3.download_file_for_os('source') + latest_python3_source = latest_python3.download_file_for_os("source") if latest_python3_source: - context['latest_python3_source'] = latest_python3_source + context["latest_python3_source"] = latest_python3_source - if 'latest_python2_source' not in context or 'latest_python3_source' not in context: + if "latest_python2_source" not in context or "latest_python3_source" not in context: return - source_content = render_to_string('downloads/download-sources-box.html', context) + source_content = render_to_string("downloads/download-sources-box.html", context) source_box, _ = Box.objects.update_or_create( - label='download-sources', + label="download-sources", defaults={ - 'content': source_content, - 'content_markup_type': 'html', - } + "content": source_content, + "content_markup_type": "html", + }, ) @@ -224,39 +228,35 @@ def update_homepage_download_box(): context = {} if latest_python2: - context['latest_python2'] = latest_python2 + context["latest_python2"] = latest_python2 if latest_python3: - context['latest_python3'] = latest_python3 + context["latest_python3"] = latest_python3 - if 'latest_python2' not in context or 'latest_python3' not in context: + if "latest_python2" not in context or "latest_python3" not in context: return - content = render_to_string('downloads/homepage-downloads-box.html', context) + content = render_to_string("downloads/homepage-downloads-box.html", context) box, _ = Box.objects.update_or_create( - label='homepage-downloads', + label="homepage-downloads", defaults={ - 'content': content, - 'content_markup_type': 'html', - } + "content": content, + "content_markup_type": "html", + }, ) @receiver(post_save, sender=Release) def promote_latest_release(sender, instance, **kwargs): - """ Promote this release to be the latest if this flag is set """ + """Promote this release to be the latest if this flag is set""" # Skip in fixtures - if kwargs.get('raw', False): + if kwargs.get("raw", False): return if instance.is_latest: # Demote all previous instances - Release.objects.filter( - version=instance.version - ).exclude( - pk=instance.pk - ).update(is_latest=False) + Release.objects.filter(version=instance.version).exclude(pk=instance.pk).update(is_latest=False) @receiver(post_save, sender=Release) @@ -265,25 +265,25 @@ def purge_fastly_download_pages(sender, instance, **kwargs): Purge Fastly caches so new Downloads show up more quickly """ # Don't purge on fixture loads - if kwargs.get('raw', False): + if kwargs.get("raw", False): return # Only purge on published instances if instance.is_published: # Purge our common pages - purge_url('/downloads/') - purge_url('/downloads/latest/python2/') - purge_url('/downloads/latest/python3/') - purge_url('/downloads/macos/') - purge_url('/downloads/source/') - purge_url('/downloads/windows/') - purge_url('/ftp/python/') + purge_url("/downloads/") + purge_url("/downloads/latest/python2/") + purge_url("/downloads/latest/python3/") + purge_url("/downloads/macos/") + purge_url("/downloads/source/") + purge_url("/downloads/windows/") + purge_url("/ftp/python/") if instance.get_version() is not None: - purge_url(f'/ftp/python/{instance.get_version()}/') + purge_url(f"/ftp/python/{instance.get_version()}/") # See issue #584 for details - purge_url('/box/supernav-python-downloads/') - purge_url('/box/homepage-downloads/') - purge_url('/box/download-sources/') + purge_url("/box/supernav-python-downloads/") + purge_url("/box/homepage-downloads/") + purge_url("/box/download-sources/") # Purge the release page itself purge_url(instance.get_absolute_url()) @@ -291,7 +291,7 @@ def purge_fastly_download_pages(sender, instance, **kwargs): @receiver(post_save, sender=Release) def update_download_supernav_and_boxes(sender, instance, **kwargs): # Skip in fixtures - if kwargs.get('raw', False): + if kwargs.get("raw", False): return if instance.is_published: @@ -308,34 +308,23 @@ class ReleaseFile(ContentManageable, NameSlugModel): versions for example Windows and MacOS 32 vs 64 bit each file needs to be added separately """ + os = models.ForeignKey( OS, - related_name='releases', - verbose_name='OS', + related_name="releases", + verbose_name="OS", on_delete=models.CASCADE, ) - release = models.ForeignKey(Release, related_name='files', on_delete=models.CASCADE) + release = models.ForeignKey(Release, related_name="files", on_delete=models.CASCADE) description = models.TextField(blank=True) - is_source = models.BooleanField('Is Source Distribution', default=False) - url = models.URLField('URL', unique=True, db_index=True, help_text="Download URL") - gpg_signature_file = models.URLField( - 'GPG SIG URL', - blank=True, - help_text="GPG Signature URL" - ) - sigstore_signature_file = models.URLField( - "Sigstore Signature URL", blank=True, help_text="Sigstore Signature URL" - ) - sigstore_cert_file = models.URLField( - "Sigstore Cert URL", blank=True, help_text="Sigstore Cert URL" - ) - sigstore_bundle_file = models.URLField( - "Sigstore Bundle URL", blank=True, help_text="Sigstore Bundle URL" - ) - sbom_spdx2_file = models.URLField( - "SPDX-2 SBOM URL", blank=True, help_text="SPDX-2 SBOM URL" - ) - md5_sum = models.CharField('MD5 Sum', max_length=200, blank=True) + is_source = models.BooleanField("Is Source Distribution", default=False) + url = models.URLField("URL", unique=True, db_index=True, help_text="Download URL") + gpg_signature_file = models.URLField("GPG SIG URL", blank=True, help_text="GPG Signature URL") + sigstore_signature_file = models.URLField("Sigstore Signature URL", blank=True, help_text="Sigstore Signature URL") + sigstore_cert_file = models.URLField("Sigstore Cert URL", blank=True, help_text="Sigstore Cert URL") + sigstore_bundle_file = models.URLField("Sigstore Bundle URL", blank=True, help_text="Sigstore Bundle URL") + sbom_spdx2_file = models.URLField("SPDX-2 SBOM URL", blank=True, help_text="SPDX-2 SBOM URL") + md5_sum = models.CharField("MD5 Sum", max_length=200, blank=True) filesize = models.IntegerField(default=0) download_button = models.BooleanField(default=False, help_text="Use for the supernav download button for this OS") @@ -343,16 +332,18 @@ def validate_unique(self, exclude=None): if self.download_button: qs = ReleaseFile.objects.filter(release=self.release, os=self.os, download_button=True).exclude(pk=self.id) if qs.count() > 0: - raise ValidationError("Only one Release File per OS can have \"Download button\" enabled") + raise ValidationError('Only one Release File per OS can have "Download button" enabled') super(ReleaseFile, self).validate_unique(exclude=exclude) class Meta: - verbose_name = 'Release File' - verbose_name_plural = 'Release Files' - ordering = ('-release__is_published', 'release__name', 'os__name', 'name') + verbose_name = "Release File" + verbose_name_plural = "Release Files" + ordering = ("-release__is_published", "release__name", "os__name", "name") constraints = [ - models.UniqueConstraint(fields=['os', 'release'], - condition=models.Q(download_button=True), - name="only_one_download_per_os_per_release"), + models.UniqueConstraint( + fields=["os", "release"], + condition=models.Q(download_button=True), + name="only_one_download_per_os_per_release", + ), ] diff --git a/downloads/search_indexes.py b/downloads/search_indexes.py index 7d476fb33..2c3d3ee98 100644 --- a/downloads/search_indexes.py +++ b/downloads/search_indexes.py @@ -10,11 +10,11 @@ class ReleaseIndex(indexes.SearchIndex, indexes.Indexable): text = indexes.CharField(document=True, use_template=True) - name = indexes.CharField(model_attr='name') + name = indexes.CharField(model_attr="name") description = indexes.CharField() path = indexes.CharField() - release_notes_url = indexes.CharField(model_attr='release_notes_url') - release_date = indexes.DateTimeField(model_attr='release_date') + release_notes_url = indexes.CharField(model_attr="release_notes_url") + release_date = indexes.DateTimeField(model_attr="release_date") include_template = indexes.CharField() @@ -22,7 +22,7 @@ def get_model(self): return Release def index_queryset(self, using=None): - """ Only index published Releases """ + """Only index published Releases""" return self.get_model().objects.filter(is_published=True) def prepare_include_template(self, obj): @@ -38,7 +38,7 @@ def prepare_description(self, obj): return striptags(truncatewords_html(obj.content.rendered, 50)) def prepare(self, obj): - """ Boost recent releases """ + """Boost recent releases""" data = super().prepare(obj) now = timezone.now() @@ -49,10 +49,10 @@ def prepare(self, obj): # Boost releases in the last 3 months and 6 months # reduce boost on releases older than 2 years if obj.release_date >= three_months: - data['boost'] = 1.2 + data["boost"] = 1.2 elif obj.release_date >= six_months: - data['boost'] = 1.1 + data["boost"] = 1.1 elif obj.release_date <= two_years: - data['boost'] = 0.8 + data["boost"] = 0.8 return data diff --git a/downloads/serializers.py b/downloads/serializers.py index 1ff57049f..656da93a1 100644 --- a/downloads/serializers.py +++ b/downloads/serializers.py @@ -4,50 +4,47 @@ class OSSerializer(serializers.HyperlinkedModelSerializer): - class Meta: model = OS - fields = ('name', 'slug', 'resource_uri') + fields = ("name", "slug", "resource_uri") class ReleaseSerializer(serializers.HyperlinkedModelSerializer): - class Meta: model = Release fields = ( - 'name', - 'slug', - 'version', - 'is_published', - 'is_latest', - 'release_date', - 'pre_release', - 'release_page', - 'release_notes_url', - 'show_on_download_page', - 'resource_uri', + "name", + "slug", + "version", + "is_published", + "is_latest", + "release_date", + "pre_release", + "release_page", + "release_notes_url", + "show_on_download_page", + "resource_uri", ) class ReleaseFileSerializer(serializers.HyperlinkedModelSerializer): - class Meta: model = ReleaseFile fields = ( - 'name', - 'slug', - 'os', - 'release', - 'description', - 'is_source', - 'url', - 'gpg_signature_file', - 'md5_sum', - 'filesize', - 'download_button', - 'resource_uri', - 'sigstore_signature_file', - 'sigstore_cert_file', - 'sigstore_bundle_file', - 'sbom_spdx2_file', + "name", + "slug", + "os", + "release", + "description", + "is_source", + "url", + "gpg_signature_file", + "md5_sum", + "filesize", + "download_button", + "resource_uri", + "sigstore_signature_file", + "sigstore_cert_file", + "sigstore_bundle_file", + "sbom_spdx2_file", ) diff --git a/downloads/templatetags/download_tags.py b/downloads/templatetags/download_tags.py index c72f6d58c..5dabe6f60 100644 --- a/downloads/templatetags/download_tags.py +++ b/downloads/templatetags/download_tags.py @@ -5,15 +5,12 @@ @register.filter def strip_minor_version(version): - return '.'.join(version.split('.')[:2]) + return ".".join(version.split(".")[:2]) @register.filter def has_sigstore_materials(files): - return any( - f.sigstore_bundle_file or f.sigstore_cert_file or f.sigstore_signature_file - for f in files - ) + return any(f.sigstore_bundle_file or f.sigstore_cert_file or f.sigstore_signature_file for f in files) @register.filter @@ -31,13 +28,13 @@ def sort_windows(files): windows_files = [] other_files = [] for preferred in ( - 'Windows installer (64-bit)', - 'Windows installer (32-bit)', - 'Windows installer (ARM64)', - 'Windows help file', - 'Windows embeddable package (64-bit)', - 'Windows embeddable package (32-bit)', - 'Windows embeddable package (ARM64)', + "Windows installer (64-bit)", + "Windows installer (32-bit)", + "Windows installer (ARM64)", + "Windows help file", + "Windows embeddable package (64-bit)", + "Windows embeddable package (32-bit)", + "Windows embeddable package (ARM64)", ): for file in files: if file.name == preferred: @@ -47,7 +44,7 @@ def sort_windows(files): # Then append any remaining Windows files for file in files: - if file.name.startswith('Windows'): + if file.name.startswith("Windows"): windows_files.append(file) else: other_files.append(file) diff --git a/downloads/tests/base.py b/downloads/tests/base.py index bcb7905c4..10d67efb7 100644 --- a/downloads/tests/base.py +++ b/downloads/tests/base.py @@ -8,84 +8,82 @@ class DownloadMixin: - @classmethod def setUpClass(cls): super().setUpClass() - cls.windows, _ = OS.objects.get_or_create(name='Windows') - cls.osx, _ = OS.objects.get_or_create(name='macOS') - cls.linux, _ = OS.objects.get_or_create(name='Linux') + cls.windows, _ = OS.objects.get_or_create(name="Windows") + cls.osx, _ = OS.objects.get_or_create(name="macOS") + cls.linux, _ = OS.objects.get_or_create(name="Linux") class BaseDownloadTests(DownloadMixin, TestCase): - def setUp(self): self.release_275_page = Page.objects.create( - title='Python 2.7.5 Release', - path='download/releases/2.7.5', - content='whatever', + title="Python 2.7.5 Release", + path="download/releases/2.7.5", + content="whatever", is_published=True, ) self.release_275 = Release.objects.create( version=Release.PYTHON2, - name='Python 2.7.5', + name="Python 2.7.5", is_latest=True, is_published=True, release_page=self.release_275_page, - release_date=timezone.now() - datetime.timedelta(days=-1) + release_date=timezone.now() - datetime.timedelta(days=-1), ) self.release_275_windows_32bit = ReleaseFile.objects.create( os=self.windows, release=self.release_275, - name='Windows x86 MSI Installer (2.7.5)', - description='Windows binary -- does not include source', - url='ftp/python/2.7.5/python-2.7.5.msi', + name="Windows x86 MSI Installer (2.7.5)", + description="Windows binary -- does not include source", + url="ftp/python/2.7.5/python-2.7.5.msi", ) self.release_275_windows_64bit = ReleaseFile.objects.create( os=self.windows, release=self.release_275, - name='Windows X86-64 MSI Installer (2.7.5)', - description='Windows AMD64 / Intel 64 / X86-64 binary -- does not include source', - url='ftp/python/2.7.5/python-2.7.5.amd64.msi' + name="Windows X86-64 MSI Installer (2.7.5)", + description="Windows AMD64 / Intel 64 / X86-64 binary -- does not include source", + url="ftp/python/2.7.5/python-2.7.5.amd64.msi", ) self.release_275_osx = ReleaseFile.objects.create( os=self.osx, release=self.release_275, - name='Mac OSX 64-bit/32-bit', - description='Mac OS X 10.6 and later', - url='ftp/python/2.7.5/python-2.7.5-macosx10.6.dmg', + name="Mac OSX 64-bit/32-bit", + description="Mac OS X 10.6 and later", + url="ftp/python/2.7.5/python-2.7.5-macosx10.6.dmg", ) self.release_275_linux = ReleaseFile.objects.create( - name='Source tarball', + name="Source tarball", os=self.linux, release=self.release_275, is_source=True, - description='Gzipped source', - url='ftp/python/2.7.5/Python-2.7.5.tgz', + description="Gzipped source", + url="ftp/python/2.7.5/Python-2.7.5.tgz", filesize=12345678, ) self.draft_release = Release.objects.create( version=Release.PYTHON3, - name='Python 9.7.2', + name="Python 9.7.2", is_published=False, release_page=self.release_275_page, ) self.draft_release_linux = ReleaseFile.objects.create( - name='Source tarball for a draft release', + name="Source tarball for a draft release", os=self.linux, release=self.draft_release, is_source=True, - description='Gzipped source', - url='ftp/python/9.7.2/Python-9.7.2.tgz', + description="Gzipped source", + url="ftp/python/9.7.2/Python-9.7.2.tgz", ) self.hidden_release = Release.objects.create( version=Release.PYTHON3, - name='Python 0.0.0', + name="Python 0.0.0", is_published=True, show_on_download_page=False, release_page=self.release_275_page, @@ -93,7 +91,7 @@ def setUp(self): self.pre_release = Release.objects.create( version=Release.PYTHON3, - name='Python 3.9.90', + name="Python 3.9.90", is_published=True, pre_release=True, show_on_download_page=True, @@ -102,9 +100,9 @@ def setUp(self): self.python_3 = Release.objects.create( version=Release.PYTHON3, - name='Python 3.10', + name="Python 3.10", is_latest=True, is_published=True, show_on_download_page=True, - release_page=self.release_275_page + release_page=self.release_275_page, ) diff --git a/downloads/tests/test_models.py b/downloads/tests/test_models.py index f27e9517d..d5e9416d4 100644 --- a/downloads/tests/test_models.py +++ b/downloads/tests/test_models.py @@ -3,10 +3,9 @@ class DownloadModelTests(BaseDownloadTests): - def test_stringification(self): - self.assertEqual(str(self.osx), 'macOS') - self.assertEqual(str(self.release_275), 'Python 2.7.5') + self.assertEqual(str(self.osx), "macOS") + self.assertEqual(str(self.release_275), "Python 2.7.5") def test_published(self): published_releases = Release.objects.published() @@ -57,18 +56,21 @@ def test_python3(self): self.assertIn(self.pre_release, versions) def test_get_version(self): - self.assertEqual(self.release_275.name, 'Python 2.7.5') - self.assertEqual(self.release_275.get_version(), '2.7.5') + self.assertEqual(self.release_275.name, "Python 2.7.5") + self.assertEqual(self.release_275.get_version(), "2.7.5") def test_get_version_27(self): - release = Release.objects.create(name='Python 2.7.12') - self.assertEqual(release.name, 'Python 2.7.12') - self.assertEqual(release.get_version(), '2.7.12') + release = Release.objects.create(name="Python 2.7.12") + self.assertEqual(release.name, "Python 2.7.12") + self.assertEqual(release.get_version(), "2.7.12") def test_get_version_invalid(self): names = [ - 'spam', 'Python2.7.5', 'Python 2.7.7', r'Python\t2.7.9', - r'\tPython 2.8.0', + "spam", + "Python2.7.5", + "Python 2.7.7", + r"Python\t2.7.9", + r"\tPython 2.8.0", ] for name in names: with self.subTest(name=name): @@ -80,10 +82,10 @@ def test_is_version_at_least(self): self.assertFalse(self.release_275.is_version_at_least_3_5) self.assertFalse(self.release_275.is_version_at_least_3_9) - release_38 = Release.objects.create(name='Python 3.8.0') + release_38 = Release.objects.create(name="Python 3.8.0") self.assertFalse(release_38.is_version_at_least_3_9) self.assert_(release_38.is_version_at_least_3_5) - release_310 = Release.objects.create(name='Python 3.10.0') + release_310 = Release.objects.create(name="Python 3.10.0") self.assert_(release_310.is_version_at_least_3_9) self.assert_(release_310.is_version_at_least_3_5) diff --git a/downloads/tests/test_views.py b/downloads/tests/test_views.py index e495b9e93..2b5734950 100644 --- a/downloads/tests/test_views.py +++ b/downloads/tests/test_views.py @@ -18,53 +18,53 @@ # We need to activate caching for throttling tests. TEST_CACHES = dict(settings.CACHES) -TEST_CACHES['default'] = { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', +TEST_CACHES["default"] = { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", } # Note that we can't override 'REST_FRAMEWORK' with 'override_settings' # because of https://github.com/encode/django-rest-framework/issues/2466. TEST_THROTTLE_RATES = { - 'anon': '1/day', - 'user': '2/day', + "anon": "1/day", + "user": "2/day", } class DownloadViewsTests(BaseDownloadTests): def test_download_full_os_list(self): - url = reverse('download:download_full_os_list') + url = reverse("download:download_full_os_list") response = self.client.get(url) self.assertEqual(response.status_code, 200) def test_download_release_detail(self): - url = reverse('download:download_release_detail', kwargs={'release_slug': self.release_275.slug}) + url = reverse("download:download_release_detail", kwargs={"release_slug": self.release_275.slug}) response = self.client.get(url) self.assertEqual(response.status_code, 200) with self.subTest("Release file sizes should be human-readable"): self.assertInHTML("11.8 MB", response.content.decode()) - url = reverse('download:download_release_detail', kwargs={'release_slug': 'fake_slug'}) + url = reverse("download:download_release_detail", kwargs={"release_slug": "fake_slug"}) response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_download_os_list(self): - url = reverse('download:download_os_list', kwargs={'slug': self.linux.slug}) + url = reverse("download:download_os_list", kwargs={"slug": self.linux.slug}) response = self.client.get(url) self.assertEqual(response.status_code, 200) def test_download(self): - url = reverse('download:download') + url = reverse("download:download") response = self.client.get(url) self.assertEqual(response.status_code, 200) def test_latest_redirects(self): latest_python2 = Release.objects.released().python2().latest() - url = reverse('download:download_latest_python2') + url = reverse("download:download_latest_python2") response = self.client.get(url) self.assertRedirects(response, latest_python2.get_absolute_url()) latest_python3 = Release.objects.released().python3().latest() - url = reverse('download:download_latest_python3') + url = reverse("download:download_latest_python3") response = self.client.get(url) self.assertRedirects(response, latest_python3.get_absolute_url()) @@ -75,8 +75,8 @@ def test_redirect_page_object_to_release_detail_page(self): self.assertRedirects( response, reverse( - 'download:download_release_detail', - kwargs={'release_slug': self.release_275.slug}, + "download:download_release_detail", + kwargs={"release_slug": self.release_275.slug}, ), status_code=301, ) @@ -86,42 +86,44 @@ class RegressionTests(DownloadMixin, TestCase): """These tests are for bugs found by Sentry.""" def test_without_latest_python3_release(self): - url = reverse('download:download') + url = reverse("download:download") response = self.client.get(url) - self.assertIsNone(response.context['latest_python2']) - self.assertIsNone(response.context['latest_python3']) - self.assertIsInstance(response.context['python_files'], list) - self.assertEqual(len(response.context['python_files']), 3) + self.assertIsNone(response.context["latest_python2"]) + self.assertIsNone(response.context["latest_python3"]) + self.assertIsInstance(response.context["python_files"], list) + self.assertEqual(len(response.context["python_files"]), 3) class BaseDownloadApiViewsTest(BaseDownloadTests, BaseAPITestCase): # This API used by add-to-pydotorg.py in python/release-tools. - app_label = 'downloads' + app_label = "downloads" def setUp(self): super().setUp() self.staff_user = UserFactory( - username='staffuser', - password='passworduser', + username="staffuser", + password="passworduser", is_staff=True, ) - self.Authorization = f'Token {self.staff_user.api_v2_token}' - self.Authorization_invalid = 'Token invalid-token' + self.Authorization = f"Token {self.staff_user.api_v2_token}" + self.Authorization_invalid = "Token invalid-token" def get_json(self, response): json_response = response.json() - if 'objects' in json_response: - return json_response['objects'] + if "objects" in json_response: + return json_response["objects"] return json_response def test_invalid_token(self): - url = self.create_url('os', self.linux.pk) + url = self.create_url("os", self.linux.pk) response = self.json_client( - 'delete', url, HTTP_AUTHORIZATION=self.Authorization_invalid, + "delete", + url, + HTTP_AUTHORIZATION=self.Authorization_invalid, ) self.assertEqual(response.status_code, 401) - url = self.create_url('os') + url = self.create_url("os") response = self.client.get(url, headers={"authorization": self.Authorization_invalid}) # TODO: API v1 returns 200 for a GET request even if token is invalid. # 'StaffAuthorization.read_list` returns 'object_list' unconditionally, @@ -129,55 +131,52 @@ def test_invalid_token(self): self.assertIn(response.status_code, [200, 401]) def test_get_os(self): - response = self.client.get(self.create_url('os')) + response = self.client.get(self.create_url("os")) self.assertEqual(response.status_code, 200) content = self.get_json(response) self.assertEqual(len(content), 3) - self.assertIn( - self.create_url('os', self.linux.pk), - content[0]['resource_uri'] - ) - self.assertEqual(content[0]['name'], self.linux.name) - self.assertEqual(content[0]['slug'], self.linux.slug) + self.assertIn(self.create_url("os", self.linux.pk), content[0]["resource_uri"]) + self.assertEqual(content[0]["name"], self.linux.name) + self.assertEqual(content[0]["slug"], self.linux.slug) # The following fields won't show up in the response # because there is no 'User' relation defined in the API. # See 'ReleaseResource.release_page' for an example. - self.assertNotIn('creator', content[0]) - self.assertNotIn('last_modified_by', content[0]) + self.assertNotIn("creator", content[0]) + self.assertNotIn("last_modified_by", content[0]) def test_post_os(self): - url = self.create_url('os') + url = self.create_url("os") data = { - 'name': 'BeOS', - 'slug': 'beos', + "name": "BeOS", + "slug": "beos", } - response = self.json_client('post', url, data) + response = self.json_client("post", url, data) self.assertEqual(response.status_code, 401) - response = self.json_client('post', url, data, HTTP_AUTHORIZATION=self.Authorization) + response = self.json_client("post", url, data, HTTP_AUTHORIZATION=self.Authorization) self.assertEqual(response.status_code, 201) # Get the new created OS object via API. - new_url = response['Location'] + new_url = response["Location"] response = self.client.get(new_url) self.assertEqual(response.status_code, 200) content = self.get_json(response) - self.assertEqual(content['name'], data['name']) - self.assertEqual(content['slug'], data['slug']) + self.assertEqual(content["name"], data["name"]) + self.assertEqual(content["slug"], data["slug"]) def test_delete_os(self): - url = self.create_url('os', self.linux.pk) + url = self.create_url("os", self.linux.pk) response = self.client.get(url) self.assertEqual(response.status_code, 200) content = self.get_json(response) - self.assertIn(url, content['resource_uri']) - self.assertEqual(content['name'], self.linux.name) - self.assertEqual(content['slug'], self.linux.slug) + self.assertIn(url, content["resource_uri"]) + self.assertEqual(content["name"], self.linux.name) + self.assertEqual(content["slug"], self.linux.slug) - response = self.json_client('delete', url) + response = self.json_client("delete", url) self.assertEqual(response.status_code, 401) - response = self.json_client('delete', url, HTTP_AUTHORIZATION=self.Authorization) + response = self.json_client("delete", url, HTTP_AUTHORIZATION=self.Authorization) self.assertEqual(response.status_code, 204) # Test that the OS doesn't exist. @@ -186,35 +185,35 @@ def test_delete_os(self): def test_filter_os(self): filters = { - 'name': self.linux.name, - 'slug': self.linux.slug, + "name": self.linux.name, + "slug": self.linux.slug, } - response = self.client.get(self.create_url('os', filters=filters)) + response = self.client.get(self.create_url("os", filters=filters)) self.assertEqual(response.status_code, 200) content = self.get_json(response) self.assertEqual(len(content), 1) - self.assertEqual(content[0]['name'], self.linux.name) - self.assertEqual(content[0]['slug'], self.linux.slug) + self.assertEqual(content[0]["name"], self.linux.name) + self.assertEqual(content[0]["slug"], self.linux.slug) - response = self.client.get(self.create_url('os', filters={'name': 'invalid'})) + response = self.client.get(self.create_url("os", filters={"name": "invalid"})) self.assertEqual(response.status_code, 200) content = self.get_json(response) self.assertEqual(len(content), 0) # To test 'exact' filtering in 'OSResource.Meta.filtering'. - response = self.client.get(self.create_url('os', filters={'name': 'linu'})) + response = self.client.get(self.create_url("os", filters={"name": "linu"})) self.assertEqual(response.status_code, 200) content = self.get_json(response) self.assertEqual(len(content), 0) # Test uppercase 'self.linux.name'. - response = self.client.get(self.create_url('os', filters={'name': self.linux.name.upper()})) + response = self.client.get(self.create_url("os", filters={"name": self.linux.name.upper()})) self.assertEqual(response.status_code, 200) content = self.get_json(response) self.assertEqual(len(content), 0) def test_get_release(self): - url = self.create_url('release') + url = self.create_url("release") response = self.client.get(url) self.assertEqual(response.status_code, 200) content = self.get_json(response) @@ -226,33 +225,29 @@ def test_get_release(self): self.assertEqual(response.status_code, 200) content = self.get_json(response) self.assertEqual(len(content), 5) - self.assertFalse(content[0]['is_latest']) + self.assertFalse(content[0]["is_latest"]) def test_post_release(self): - release_page = PageFactory( - title='python 3.3', - path='/rels/3-3/', - content='python 3.3. released' - ) - release_page_url = self.create_url('page', release_page.pk, app_label='pages') + release_page = PageFactory(title="python 3.3", path="/rels/3-3/", content="python 3.3. released") + release_page_url = self.create_url("page", release_page.pk, app_label="pages") response = self.client.get(release_page_url) self.assertEqual(response.status_code, 200) - url = self.create_url('release') + url = self.create_url("release") data = { - 'name': 'python 3.3', - 'slug': 'py3-3', - 'release_page': release_page_url, - 'is_latest': True, + "name": "python 3.3", + "slug": "py3-3", + "release_page": release_page_url, + "is_latest": True, } - response = self.json_client('post', url, data) + response = self.json_client("post", url, data) self.assertEqual(response.status_code, 401) - response = self.json_client('post', url, data, HTTP_AUTHORIZATION=self.Authorization) + response = self.json_client("post", url, data, HTTP_AUTHORIZATION=self.Authorization) self.assertEqual(response.status_code, 201) # Test that the release is created. - new_url = response['Location'] + new_url = response["Location"] # We'll get 401 because the default value of # 'Release.is_published' is False. response = self.client.get(new_url) @@ -261,24 +256,24 @@ def test_post_release(self): response = self.client.get(new_url, headers={"authorization": self.Authorization}) self.assertEqual(response.status_code, 200) content = self.get_json(response) - self.assertEqual(content['name'], data['name']) - self.assertEqual(content['slug'], data['slug']) - self.assertTrue(content['is_latest']) - self.assertIn(data['release_page'], content['release_page']) + self.assertEqual(content["name"], data["name"]) + self.assertEqual(content["slug"], data["slug"]) + self.assertTrue(content["is_latest"]) + self.assertIn(data["release_page"], content["release_page"]) def test_delete_release(self): - url = self.create_url('release', self.release_275.pk) + url = self.create_url("release", self.release_275.pk) response = self.client.get(url) self.assertEqual(response.status_code, 200) content = self.get_json(response) - self.assertIn(url, content['resource_uri']) - self.assertEqual(content['name'], self.release_275.name) - self.assertEqual(content['slug'], self.release_275.slug) + self.assertIn(url, content["resource_uri"]) + self.assertEqual(content["name"], self.release_275.name) + self.assertEqual(content["slug"], self.release_275.slug) - response = self.json_client('delete', url) + response = self.json_client("delete", url) self.assertEqual(response.status_code, 401) - response = self.json_client('delete', url, HTTP_AUTHORIZATION=self.Authorization) + response = self.json_client("delete", url, HTTP_AUTHORIZATION=self.Authorization) self.assertEqual(response.status_code, 204) # Test that the OS doesn't exist. @@ -286,83 +281,77 @@ def test_delete_release(self): self.assertEqual(response.status_code, 404) def test_filter_release(self): - response = self.client.get(self.create_url('release', filters={'pre_release': True})) + response = self.client.get(self.create_url("release", filters={"pre_release": True})) self.assertEqual(response.status_code, 200) content = self.get_json(response) self.assertEqual(len(content), 1) - self.assertIn( - self.create_url('release', self.pre_release.pk), - content[0]['resource_uri'] - ) - self.assertEqual(content[0]['name'], self.pre_release.name) - self.assertEqual(content[0]['slug'], self.pre_release.slug) - self.assertTrue(content[0]['is_published']) - self.assertTrue(content[0]['pre_release']) - self.assertTrue(content[0]['show_on_download_page']) - self.assertEqual(content[0]['version'], self.pre_release.version) - self.assertEqual( - content[0]['release_notes_url'], - self.pre_release.release_notes_url - ) + self.assertIn(self.create_url("release", self.pre_release.pk), content[0]["resource_uri"]) + self.assertEqual(content[0]["name"], self.pre_release.name) + self.assertEqual(content[0]["slug"], self.pre_release.slug) + self.assertTrue(content[0]["is_published"]) + self.assertTrue(content[0]["pre_release"]) + self.assertTrue(content[0]["show_on_download_page"]) + self.assertEqual(content[0]["version"], self.pre_release.version) + self.assertEqual(content[0]["release_notes_url"], self.pre_release.release_notes_url) def test_get_release_file(self): - url = self.create_url('release_file') + url = self.create_url("release_file") response = self.client.get(url) self.assertEqual(response.status_code, 200) content = self.get_json(response) self.assertEqual(len(content), 5) - url = self.create_url('release_file', self.release_275_linux.pk) + url = self.create_url("release_file", self.release_275_linux.pk) response = self.client.get(url) self.assertEqual(response.status_code, 200) content = self.get_json(response) - self.assertEqual(content['name'], self.release_275_linux.name) + self.assertEqual(content["name"], self.release_275_linux.name) - url = self.create_url('release_file', 9999999) + url = self.create_url("release_file", 9999999) response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_post_release_file(self): - url = self.create_url('release_file') + url = self.create_url("release_file") data = { - 'name': 'File name', - 'slug': 'file-name', - 'os': self.create_url('os', self.linux.pk), - 'release': self.create_url('release', self.release_275.pk), - 'description': 'This is a description.', - 'is_source': True, - 'url': 'https://www.python.org/', - 'md5_sum': '098f6bcd4621d373cade4e832627b4f6', - 'filesize': len('098f6bcd4621d373cade4e832627b4f6'), - 'download_button': True, + "name": "File name", + "slug": "file-name", + "os": self.create_url("os", self.linux.pk), + "release": self.create_url("release", self.release_275.pk), + "description": "This is a description.", + "is_source": True, + "url": "https://www.python.org/", + "md5_sum": "098f6bcd4621d373cade4e832627b4f6", + "filesize": len("098f6bcd4621d373cade4e832627b4f6"), + "download_button": True, } - response = self.json_client('post', url, data) + response = self.json_client("post", url, data) self.assertEqual(response.status_code, 401) - response = self.json_client('post', url, data, HTTP_AUTHORIZATION=self.Authorization) + response = self.json_client("post", url, data, HTTP_AUTHORIZATION=self.Authorization) self.assertEqual(response.status_code, 201) # Test that the file is created. - new_url = response['Location'] + new_url = response["Location"] response = self.client.get(new_url) self.assertEqual(response.status_code, 200) content = self.get_json(response) - self.assertEqual(content['name'], data['name']) - self.assertEqual(content['slug'], data['slug']) + self.assertEqual(content["name"], data["name"]) + self.assertEqual(content["slug"], data["slug"]) # 'gpg_signature_file' is optional. - self.assertEqual(content['gpg_signature_file'], '') - self.assertTrue(content['is_source']) - self.assertTrue(content['download_button']) - self.assertIn(data['os'], content['os']) - self.assertIn(data['release'], content['release']) - self.assertEqual(content['description'], data['description']) + self.assertEqual(content["gpg_signature_file"], "") + self.assertTrue(content["is_source"]) + self.assertTrue(content["download_button"]) + self.assertIn(data["os"], content["os"]) + self.assertIn(data["release"], content["release"]) + self.assertEqual(content["description"], data["description"]) def test_delete_release_file(self): - url = self.create_url('release_file', self.release_275_linux.pk) - response = self.json_client('delete', url) + url = self.create_url("release_file", self.release_275_linux.pk) + response = self.json_client("delete", url) self.assertEqual(response.status_code, 401) - response = self.json_client('delete', url, HTTP_AUTHORIZATION=self.Authorization) + response = self.json_client("delete", url, HTTP_AUTHORIZATION=self.Authorization) self.assertEqual(response.status_code, 204) # Test that the OS doesn't exist. @@ -371,41 +360,28 @@ def test_delete_release_file(self): def test_filter_release_file(self): # We'll get 400 because 'exact' is not an allowed filter. - response = self.client.get( - self.create_url('release_file', filters={'description': 'windows'}) - ) + response = self.client.get(self.create_url("release_file", filters={"description": "windows"})) self.assertEqual(response.status_code, 400) content = self.get_json(response) - self.assertIn('error', content) - self.assertIn( - '\'exact\' is not an allowed filter on the \'description\' field.', - content['error'] - ) + self.assertIn("error", content) + self.assertIn("'exact' is not an allowed filter on the 'description' field.", content["error"]) - response = self.client.get( - self.create_url('release_file', filters={'description__contains': 'Windows'}) - ) + response = self.client.get(self.create_url("release_file", filters={"description__contains": "Windows"})) self.assertEqual(response.status_code, 200) content = self.get_json(response) self.assertEqual(len(content), 2) - response = self.client.get( - self.create_url('release_file', filters={'name': self.release_275_linux.name}) - ) + response = self.client.get(self.create_url("release_file", filters={"name": self.release_275_linux.name})) self.assertEqual(response.status_code, 200) content = self.get_json(response) self.assertEqual(len(content), 1) - response = self.client.get( - self.create_url('release_file', filters={'os': self.windows.pk}) - ) + response = self.client.get(self.create_url("release_file", filters={"os": self.windows.pk})) self.assertEqual(response.status_code, 200) content = self.get_json(response) self.assertEqual(len(content), 2) - response = self.client.get( - self.create_url('release_file', filters={'release': self.release_275.pk}) - ) + response = self.client.get(self.create_url("release_file", filters={"release": self.release_275.pk})) self.assertEqual(response.status_code, 200) content = self.get_json(response) self.assertEqual(len(content), 4) @@ -413,11 +389,11 @@ def test_filter_release_file(self): # Combine two filters in one request. response = self.client.get( self.create_url( - 'release_file', + "release_file", filters={ - 'release': self.release_275.pk, - 'os': self.linux.pk, - } + "release": self.release_275.pk, + "os": self.linux.pk, + }, ) ) self.assertEqual(response.status_code, 200) @@ -426,9 +402,7 @@ def test_filter_release_file(self): # Files for a draft release should be shown to users. # TODO: We may deprecate this behavior when we drop API v1. - response = self.client.get( - self.create_url('release_file', filters={'release': self.draft_release.pk}) - ) + response = self.client.get(self.create_url("release_file", filters={"release": self.draft_release.pk})) self.assertFalse(self.draft_release.is_published) self.assertEqual(response.status_code, 200) content = self.get_json(response) @@ -436,34 +410,37 @@ def test_filter_release_file(self): class DownloadApiV1ViewsTest(BaseDownloadApiViewsTest, BaseDownloadTests): - api_version = 'v1' + api_version = "v1" def setUp(self): super().setUp() self.staff_key = self.staff_user.api_key.key - self.token_header = 'ApiKey' - self.Authorization = '{} {}:{}'.format( - self.token_header, self.staff_user.username, self.staff_key, + self.token_header = "ApiKey" + self.Authorization = "{} {}:{}".format( + self.token_header, + self.staff_user.username, + self.staff_key, ) - self.Authorization_invalid = '%s invalid:token' % self.token_header + self.Authorization_invalid = "%s invalid:token" % self.token_header class DownloadApiV2ViewsTest(BaseDownloadApiViewsTest, BaseDownloadTests, APITestCase): - api_version = 'v2' + api_version = "v2" def setUp(self): super().setUp() self.staff_key = self.staff_user.auth_token.key - self.token_header = 'Token' - self.Authorization = f'{self.token_header} {self.staff_key}' - self.Authorization_invalid = '%s invalidtoken' % self.token_header + self.token_header = "Token" + self.Authorization = f"{self.token_header} {self.staff_key}" + self.Authorization_invalid = "%s invalidtoken" % self.token_header self.normal_user = UserFactory( - username='normaluser', - password='password', + username="normaluser", + password="password", ) self.normal_user_key = self.normal_user.auth_token.key - self.Authorization_normal = '{} {}'.format( - self.token_header, self.normal_user_key, + self.Authorization_normal = "{} {}".format( + self.token_header, + self.normal_user_key, ) def get_json(self, response): @@ -471,11 +448,11 @@ def get_json(self, response): @override_settings(CACHES=TEST_CACHES) @mock.patch( - 'rest_framework.throttling.SimpleRateThrottle.THROTTLE_RATES', + "rest_framework.throttling.SimpleRateThrottle.THROTTLE_RATES", new=TEST_THROTTLE_RATES, ) def test_throttling_anon(self): - url = self.create_url('os') + url = self.create_url("os") response = self.client.get(url) self.assertEqual(response.status_code, 200) @@ -485,11 +462,11 @@ def test_throttling_anon(self): @override_settings(CACHES=TEST_CACHES) @mock.patch( - 'rest_framework.throttling.SimpleRateThrottle.THROTTLE_RATES', + "rest_framework.throttling.SimpleRateThrottle.THROTTLE_RATES", new=TEST_THROTTLE_RATES, ) def test_throttling_user(self): - url = self.create_url('os') + url = self.create_url("os") response = self.client.get(url, headers={"authorization": self.Authorization}) self.assertEqual(response.status_code, 200) @@ -506,21 +483,19 @@ def test_filter_release_file_delete_by_release(self): # -list views. # Delete all files of a release. response = self.json_client( - 'delete', + "delete", # TODO: Find a way to use view.reverse_action() at # http://www.django-rest-framework.org/api-guide/viewsets/#reversing-action-urls self.create_url( - 'release_file/delete_by_release', - filters={'release': self.release_275.pk}, + "release_file/delete_by_release", + filters={"release": self.release_275.pk}, ), HTTP_AUTHORIZATION=self.Authorization, ) self.assertEqual(response.status_code, 204) # Making a GET request after the deletion shouldn't return any results. - response = self.client.get( - self.create_url('release_file', filters={'release': self.release_275.pk}) - ) + response = self.client.get(self.create_url("release_file", filters={"release": self.release_275.pk})) self.assertEqual(response.status_code, 200) content = self.get_json(response) self.assertEqual(len(content), 0) @@ -528,10 +503,10 @@ def test_filter_release_file_delete_by_release(self): # Making a valid request should return 403 Forbidden if it # comes from a non-staff user. response = self.json_client( - 'delete', + "delete", self.create_url( - 'release_file/delete_by_release', - filters={'release': self.release_275.pk}, + "release_file/delete_by_release", + filters={"release": self.release_275.pk}, ), HTTP_AUTHORIZATION=self.Authorization_normal, ) @@ -540,8 +515,8 @@ def test_filter_release_file_delete_by_release(self): # Calling /release_file/delete_by_release/ with no '?release=N' should # return 400. response = self.json_client( - 'delete', - self.create_url('release_file/delete_by_release'), + "delete", + self.create_url("release_file/delete_by_release"), HTTP_AUTHORIZATION=self.Authorization, ) self.assertEqual(response.status_code, 400) @@ -549,9 +524,9 @@ def test_filter_release_file_delete_by_release(self): # /release_file/delete_by_release/ should only accept DELETE requests. response = self.client.get( self.create_url( - 'release_file/delete_by_release', - filters={'release': self.release_275.pk}, + "release_file/delete_by_release", + filters={"release": self.release_275.pk}, ), - headers={"authorization": self.Authorization} + headers={"authorization": self.Authorization}, ) self.assertEqual(response.status_code, 405) diff --git a/downloads/urls.py b/downloads/urls.py index d64f0a1ad..ec374079a 100644 --- a/downloads/urls.py +++ b/downloads/urls.py @@ -1,12 +1,12 @@ from . import views from django.urls import path, re_path -app_name = 'downloads' +app_name = "downloads" urlpatterns = [ - re_path(r'latest/python2/?$', views.DownloadLatestPython2.as_view(), name='download_latest_python2'), - re_path(r'latest/python3/?$', views.DownloadLatestPython3.as_view(), name='download_latest_python3'), - path('operating-systems/', views.DownloadFullOSList.as_view(), name='download_full_os_list'), - path('release//', views.DownloadReleaseDetail.as_view(), name='download_release_detail'), - path('/', views.DownloadOSList.as_view(), name='download_os_list'), - path('', views.DownloadHome.as_view(), name='download'), + re_path(r"latest/python2/?$", views.DownloadLatestPython2.as_view(), name="download_latest_python2"), + re_path(r"latest/python3/?$", views.DownloadLatestPython3.as_view(), name="download_latest_python3"), + path("operating-systems/", views.DownloadFullOSList.as_view(), name="download_full_os_list"), + path("release//", views.DownloadReleaseDetail.as_view(), name="download_release_detail"), + path("/", views.DownloadOSList.as_view(), name="download_os_list"), + path("", views.DownloadHome.as_view(), name="download"), ] diff --git a/downloads/views.py b/downloads/views.py index 746845402..85ebd8c0a 100644 --- a/downloads/views.py +++ b/downloads/views.py @@ -7,7 +7,8 @@ class DownloadLatestPython2(RedirectView): - """ Redirect to latest Python 2 release """ + """Redirect to latest Python 2 release""" + permanent = False def get_redirect_url(self, **kwargs): @@ -19,11 +20,12 @@ def get_redirect_url(self, **kwargs): if latest_python2: return latest_python2.get_absolute_url() else: - return reverse('download') + return reverse("download") class DownloadLatestPython3(RedirectView): - """ Redirect to latest Python 3 release """ + """Redirect to latest Python 3 release""" + permanent = False def get_redirect_url(self, **kwargs): @@ -35,22 +37,25 @@ def get_redirect_url(self, **kwargs): if latest_python3: return latest_python3.get_absolute_url() else: - return reverse('download') + return reverse("download") class DownloadBase: - """ Include latest releases in all views """ + """Include latest releases in all views""" + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context.update({ - 'latest_python2': Release.objects.latest_python2(), - 'latest_python3': Release.objects.latest_python3(), - }) + context.update( + { + "latest_python2": Release.objects.latest_python2(), + "latest_python3": Release.objects.latest_python3(), + } + ) return context class DownloadHome(DownloadBase, TemplateView): - template_name = 'downloads/index.html' + template_name = "downloads/index.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -67,62 +72,69 @@ def get_context_data(self, **kwargs): python_files = [] for o in OS.objects.all(): data = { - 'os': o, + "os": o, } if latest_python2 is not None: - data['python2'] = latest_python2.download_file_for_os(o.slug) + data["python2"] = latest_python2.download_file_for_os(o.slug) if latest_python3 is not None: - data['python3'] = latest_python3.download_file_for_os(o.slug) + data["python3"] = latest_python3.download_file_for_os(o.slug) python_files.append(data) - context.update({ - 'releases': Release.objects.downloads(), - 'latest_python2': latest_python2, - 'latest_python3': latest_python3, - 'python_files': python_files, - }) + context.update( + { + "releases": Release.objects.downloads(), + "latest_python2": latest_python2, + "latest_python3": latest_python3, + "python_files": python_files, + } + ) return context class DownloadFullOSList(DownloadBase, ListView): - template_name = 'downloads/full_os_list.html' - context_object_name = 'os_list' + template_name = "downloads/full_os_list.html" + context_object_name = "os_list" model = OS class DownloadOSList(DownloadBase, DetailView): - template_name = 'downloads/os_list.html' - context_object_name = 'os' + template_name = "downloads/os_list.html" + context_object_name = "os" model = OS def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) release_files = ReleaseFile.objects.select_related( - 'os', + "os", ).filter(os=self.object) - context.update({ - 'os_slug': self.object.slug, - 'releases': Release.objects.released().prefetch_related( - Prefetch('files', queryset=release_files), - ).order_by('-release_date'), - 'pre_releases': Release.objects.published().pre_release().prefetch_related( - Prefetch('files', queryset=release_files), - ).order_by('-release_date'), - }) + context.update( + { + "os_slug": self.object.slug, + "releases": Release.objects.released() + .prefetch_related( + Prefetch("files", queryset=release_files), + ) + .order_by("-release_date"), + "pre_releases": Release.objects.published() + .pre_release() + .prefetch_related( + Prefetch("files", queryset=release_files), + ) + .order_by("-release_date"), + } + ) return context class DownloadReleaseDetail(DownloadBase, DetailView): - template_name = 'downloads/release_detail.html' + template_name = "downloads/release_detail.html" model = Release - context_object_name = 'release' + context_object_name = "release" def get_object(self): try: - return self.get_queryset().select_related().get( - slug=self.kwargs['release_slug'] - ) + return self.get_queryset().select_related().get(slug=self.kwargs["release_slug"]) except self.model.DoesNotExist: raise Http404 @@ -130,20 +142,12 @@ def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Manually add release files for better ordering - context['release_files'] = [] + context["release_files"] = [] # Add source files - context['release_files'].extend( - list(self.object.files.filter(os__slug='source').order_by('name')) - ) + context["release_files"].extend(list(self.object.files.filter(os__slug="source").order_by("name"))) # Add all other OSes - context['release_files'].extend( - list( - self.object.files.exclude( - os__slug='source' - ).order_by('os__slug', 'name') - ) - ) + context["release_files"].extend(list(self.object.files.exclude(os__slug="source").order_by("os__slug", "name"))) return context diff --git a/events/admin.py b/events/admin.py index ba03c28ed..2e5be3f45 100644 --- a/events/admin.py +++ b/events/admin.py @@ -28,15 +28,15 @@ class AlarmInline(admin.StackedInline): @admin.register(Event) class EventAdmin(ContentManageableModelAdmin): inlines = [OccurringRuleInline, RecurringRuleInline] - list_display = ['__str__', 'calendar', 'featured'] - list_filter = ['calendar', 'featured'] - raw_id_fields = ['venue'] - search_fields = ['title'] + list_display = ["__str__", "calendar", "featured"] + list_filter = ["calendar", "featured"] + raw_id_fields = ["venue"] + search_fields = ["title"] @admin.register(EventLocation) class EventLocationAdmin(admin.ModelAdmin): - list_filter = ['calendar'] + list_filter = ["calendar"] admin.site.register(EventCategory, NameSlugAdmin) diff --git a/events/apps.py b/events/apps.py index 23eadd3fe..0de050e9f 100644 --- a/events/apps.py +++ b/events/apps.py @@ -2,5 +2,4 @@ class EventsAppConfig(AppConfig): - - name = 'events' + name = "events" diff --git a/events/factories.py b/events/factories.py index deef85b38..97ee7adfb 100644 --- a/events/factories.py +++ b/events/factories.py @@ -5,40 +5,39 @@ class CalendarFactory(DjangoModelFactory): - class Meta: model = Calendar - django_get_or_create = ('slug',) + django_get_or_create = ("slug",) - name = factory.Sequence(lambda n: f'Calendar {n}') + name = factory.Sequence(lambda n: f"Calendar {n}") def initial_data(): return { - 'calendars': [ + "calendars": [ CalendarFactory( - name='Python Events Calendar', - slug='python-events-calendar', - twitter='https://twitter.com/PythonEvents', - url='https://www.google.com/calendar/ical/j7gov1cmnqr9tvg14k62' - '1j7t5c@group.calendar.google.com/public/basic.ics', - rss='https://www.google.com/calendar/feeds/j7gov1cmnqr9tvg14k6' - '21j7t5c@group.calendar.google.com/public/basic?orderby=st' - 'arttime&sortorder=ascending&futureevents=true', - embed='https://www.google.com/calendar/embed?src=j7gov1cmnqr9t' - 'vg14k621j7t5c@group.calendar.google.com&ctz=Europe/London', + name="Python Events Calendar", + slug="python-events-calendar", + twitter="https://twitter.com/PythonEvents", + url="https://www.google.com/calendar/ical/j7gov1cmnqr9tvg14k62" + "1j7t5c@group.calendar.google.com/public/basic.ics", + rss="https://www.google.com/calendar/feeds/j7gov1cmnqr9tvg14k6" + "21j7t5c@group.calendar.google.com/public/basic?orderby=st" + "arttime&sortorder=ascending&futureevents=true", + embed="https://www.google.com/calendar/embed?src=j7gov1cmnqr9t" + "vg14k621j7t5c@group.calendar.google.com&ctz=Europe/London", ), CalendarFactory( - name='Python User Group Calendar', - slug='python-user-group-calendar', - twitter='https://twitter.com/PythonEvents', - url='https://www.google.com/calendar/ical/3haig2m9msslkpf2tn1h' - '56nn9g@group.calendar.google.com/public/basic.ics', - rss='https://www.google.com/calendar/feeds/3haig2m9msslkpf2tn1' - 'h56nn9g@group.calendar.google.com/public/basic?orderby=st' - 'arttime&sortorder=ascending&futureevents=true', - embed='https://www.google.com/calendar/embed?src=3haig2m9msslk' - 'pf2tn1h56nn9g@group.calendar.google.com&ctz=Europe/London', + name="Python User Group Calendar", + slug="python-user-group-calendar", + twitter="https://twitter.com/PythonEvents", + url="https://www.google.com/calendar/ical/3haig2m9msslkpf2tn1h" + "56nn9g@group.calendar.google.com/public/basic.ics", + rss="https://www.google.com/calendar/feeds/3haig2m9msslkpf2tn1" + "h56nn9g@group.calendar.google.com/public/basic?orderby=st" + "arttime&sortorder=ascending&futureevents=true", + embed="https://www.google.com/calendar/embed?src=3haig2m9msslk" + "pf2tn1h56nn9g@group.calendar.google.com&ctz=Europe/London", ), ], } diff --git a/events/forms.py b/events/forms.py index 7b35d9412..1d10d921b 100644 --- a/events/forms.py +++ b/events/forms.py @@ -8,42 +8,35 @@ def set_placeholder(value): - return forms.TextInput(attrs={'placeholder': value, 'required': 'required'}) + return forms.TextInput(attrs={"placeholder": value, "required": "required"}) class EventForm(forms.Form): - event_name = forms.CharField(widget=set_placeholder( - 'Name of the event (including the user group name for ' - 'user group events)' - )) - event_type = forms.CharField(widget=set_placeholder( - 'conference, bar camp, sprint, user group meeting, etc.' - )) - python_focus = forms.CharField(widget=set_placeholder( - 'Data analytics, Web Development, Country-wide conference, etc...' - )) - expected_attendees = forms.CharField(widget=set_placeholder('300+')) - location = forms.CharField(widget=set_placeholder( - 'IFEMA building, Madrid, Spain' - )) + event_name = forms.CharField( + widget=set_placeholder("Name of the event (including the user group name for " "user group events)") + ) + event_type = forms.CharField(widget=set_placeholder("conference, bar camp, sprint, user group meeting, etc.")) + python_focus = forms.CharField( + widget=set_placeholder("Data analytics, Web Development, Country-wide conference, etc...") + ) + expected_attendees = forms.CharField(widget=set_placeholder("300+")) + location = forms.CharField(widget=set_placeholder("IFEMA building, Madrid, Spain")) date_from = forms.DateField(widget=forms.SelectDateWidget()) date_to = forms.DateField(widget=forms.SelectDateWidget()) - recurrence = forms.CharField(widget=set_placeholder( - 'None, every second Thursday, monthly, etc.' - )) - link = forms.URLField(label='Website URL') + recurrence = forms.CharField(widget=set_placeholder("None, every second Thursday, monthly, etc.")) + link = forms.URLField(label="Website URL") description = forms.CharField(widget=forms.Textarea) def send_email(self, creator): context = { - 'event': self.cleaned_data, - 'creator': creator, - 'site': Site.objects.get_current(), + "event": self.cleaned_data, + "creator": creator, + "site": Site.objects.get_current(), } - text_message_template = loader.get_template('events/email/new_event.txt') + text_message_template = loader.get_template("events/email/new_event.txt") text_message = text_message_template.render(context) send_mail( - subject='New event submission: "{}"'.format(self.cleaned_data['event_name']), + subject='New event submission: "{}"'.format(self.cleaned_data["event_name"]), message=text_message, from_email=creator.email, recipient_list=[settings.EVENTS_TO_EMAIL], diff --git a/events/importer.py b/events/importer.py index e47775060..19727e7e2 100644 --- a/events/importer.py +++ b/events/importer.py @@ -21,35 +21,29 @@ def import_occurrence(self, event, event_data): # Django will already convert to datetime by setting the time to 0:00, # but won't add any timezone information. We will convert them to # aware datetime objects manually. - dt_start = extract_date_or_datetime(event_data['DTSTART'].dt) - dt_end = extract_date_or_datetime(event_data['DTEND'].dt) + dt_start = extract_date_or_datetime(event_data["DTSTART"].dt) + dt_end = extract_date_or_datetime(event_data["DTEND"].dt) # Let's mark those occurrences as 'all-day'. - all_day = ( - dt_start.resolution == DATE_RESOLUTION or - dt_end.resolution == DATE_RESOLUTION - ) + all_day = dt_start.resolution == DATE_RESOLUTION or dt_end.resolution == DATE_RESOLUTION defaults = { - 'dt_start': dt_start, - 'dt_end': dt_end - timedelta(days=1) if all_day else dt_end, - 'all_day': all_day + "dt_start": dt_start, + "dt_end": dt_end - timedelta(days=1) if all_day else dt_end, + "all_day": all_day, } OccurringRule.objects.update_or_create(event=event, defaults=defaults) def import_event(self, event_data): - uid = event_data['UID'] - title = event_data['SUMMARY'] - description = event_data.get('DESCRIPTION', '') - location, _ = EventLocation.objects.get_or_create( - calendar=self.calendar, - name=event_data['LOCATION'] - ) + uid = event_data["UID"] + title = event_data["SUMMARY"] + description = event_data.get("DESCRIPTION", "") + location, _ = EventLocation.objects.get_or_create(calendar=self.calendar, name=event_data["LOCATION"]) defaults = { - 'title': title, - 'venue': location, - 'calendar': self.calendar, + "title": title, + "venue": location, + "calendar": self.calendar, } event, _ = Event.objects.update_or_create(uid=uid, defaults=defaults) event.description.raw = description @@ -69,7 +63,7 @@ def import_events(self, url=None): def get_events(self, ical): ical = ICalendar.from_ical(ical) - return ical.walk('VEVENT') + return ical.walk("VEVENT") def import_events_from_text(self, ical): events = self.get_events(ical) diff --git a/events/migrations/0001_initial.py b/events/migrations/0001_initial.py index 344633b3a..58a6a1cb6 100644 --- a/events/migrations/0001_initial.py +++ b/events/migrations/0001_initial.py @@ -6,155 +6,233 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='Alarm', + name="Alarm", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('trigger', models.PositiveSmallIntegerField(verbose_name='hours before the event occurs', default=24)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='events_alarm_creator', blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("trigger", models.PositiveSmallIntegerField(verbose_name="hours before the event occurs", default=24)), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="events_alarm_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, bases=(models.Model,), ), migrations.CreateModel( - name='Calendar', + name="Calendar", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('url', models.URLField(verbose_name='URL iCal', blank=True, null=True)), - ('rss', models.URLField(verbose_name='RSS Feed', blank=True, null=True)), - ('embed', models.URLField(verbose_name='URL embed', blank=True, null=True)), - ('twitter', models.URLField(verbose_name='Twitter feed', blank=True, null=True)), - ('name', models.CharField(max_length=100)), - ('slug', models.SlugField(unique=True)), - ('description', models.CharField(max_length=255, blank=True, null=True)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='events_calendar_creator', blank=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='events_calendar_modified', blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("url", models.URLField(verbose_name="URL iCal", blank=True, null=True)), + ("rss", models.URLField(verbose_name="RSS Feed", blank=True, null=True)), + ("embed", models.URLField(verbose_name="URL embed", blank=True, null=True)), + ("twitter", models.URLField(verbose_name="Twitter feed", blank=True, null=True)), + ("name", models.CharField(max_length=100)), + ("slug", models.SlugField(unique=True)), + ("description", models.CharField(max_length=255, blank=True, null=True)), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="events_calendar_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="events_calendar_modified", + blank=True, + on_delete=models.CASCADE, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, bases=(models.Model,), ), migrations.CreateModel( - name='Event', + name="Event", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('uid', models.CharField(max_length=200, blank=True, null=True)), - ('title', models.CharField(max_length=200)), - ('description', markupfield.fields.MarkupField(rendered_field=True)), - ('description_markup_type', models.CharField(max_length=30, choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], default='restructuredtext')), - ('_description_rendered', models.TextField(editable=False)), - ('featured', models.BooleanField(db_index=True, default=False)), - ('calendar', models.ForeignKey(to='events.Calendar', related_name='events', on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("uid", models.CharField(max_length=200, blank=True, null=True)), + ("title", models.CharField(max_length=200)), + ("description", markupfield.fields.MarkupField(rendered_field=True)), + ( + "description_markup_type", + models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + default="restructuredtext", + ), + ), + ("_description_rendered", models.TextField(editable=False)), + ("featured", models.BooleanField(db_index=True, default=False)), + ("calendar", models.ForeignKey(to="events.Calendar", related_name="events", on_delete=models.CASCADE)), ], options={ - 'ordering': ('-occurring_rule__dt_start',), + "ordering": ("-occurring_rule__dt_start",), }, bases=(models.Model,), ), migrations.CreateModel( - name='EventCategory', + name="EventCategory", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200)), - ('slug', models.SlugField(unique=True)), - ('calendar', models.ForeignKey(null=True, to='events.Calendar', related_name='categories', blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=200)), + ("slug", models.SlugField(unique=True)), + ( + "calendar", + models.ForeignKey( + null=True, to="events.Calendar", related_name="categories", blank=True, on_delete=models.CASCADE + ), + ), ], options={ - 'verbose_name_plural': 'event categories', - 'ordering': ('name',), + "verbose_name_plural": "event categories", + "ordering": ("name",), }, bases=(models.Model,), ), migrations.CreateModel( - name='EventLocation', + name="EventLocation", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255)), - ('address', models.CharField(max_length=255, blank=True, null=True)), - ('url', models.URLField(verbose_name='URL', blank=True, null=True)), - ('calendar', models.ForeignKey(null=True, to='events.Calendar', related_name='locations', blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=255)), + ("address", models.CharField(max_length=255, blank=True, null=True)), + ("url", models.URLField(verbose_name="URL", blank=True, null=True)), + ( + "calendar", + models.ForeignKey( + null=True, to="events.Calendar", related_name="locations", blank=True, on_delete=models.CASCADE + ), + ), ], options={ - 'ordering': ('name',), + "ordering": ("name",), }, bases=(models.Model,), ), migrations.CreateModel( - name='OccurringRule', + name="OccurringRule", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('dt_start', models.DateTimeField(default=django.utils.timezone.now)), - ('dt_end', models.DateTimeField(default=django.utils.timezone.now)), - ('event', models.OneToOneField(to='events.Event', related_name='occurring_rule', on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("dt_start", models.DateTimeField(default=django.utils.timezone.now)), + ("dt_end", models.DateTimeField(default=django.utils.timezone.now)), + ( + "event", + models.OneToOneField(to="events.Event", related_name="occurring_rule", on_delete=models.CASCADE), + ), ], - options={ - }, + options={}, bases=(events.models.RuleMixin, models.Model), ), migrations.CreateModel( - name='RecurringRule', + name="RecurringRule", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('begin', models.DateTimeField(default=django.utils.timezone.now)), - ('finish', models.DateTimeField(default=django.utils.timezone.now)), - ('duration', models.CharField(max_length=50, default='15 min')), - ('interval', models.PositiveSmallIntegerField(default=1)), - ('frequency', models.PositiveSmallIntegerField(verbose_name=((0, 'year(s)'), (1, 'month(s)'), (2, 'week(s)'), (3, 'day(s)')), default=2)), - ('event', models.ForeignKey(to='events.Event', related_name='recurring_rules', on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("begin", models.DateTimeField(default=django.utils.timezone.now)), + ("finish", models.DateTimeField(default=django.utils.timezone.now)), + ("duration", models.CharField(max_length=50, default="15 min")), + ("interval", models.PositiveSmallIntegerField(default=1)), + ( + "frequency", + models.PositiveSmallIntegerField( + verbose_name=((0, "year(s)"), (1, "month(s)"), (2, "week(s)"), (3, "day(s)")), default=2 + ), + ), + ( + "event", + models.ForeignKey(to="events.Event", related_name="recurring_rules", on_delete=models.CASCADE), + ), ], - options={ - }, + options={}, bases=(events.models.RuleMixin, models.Model), ), migrations.AddField( - model_name='event', - name='categories', - field=models.ManyToManyField(null=True, to='events.EventCategory', related_name='events', blank=True), + model_name="event", + name="categories", + field=models.ManyToManyField(null=True, to="events.EventCategory", related_name="events", blank=True), preserve_default=True, ), migrations.AddField( - model_name='event', - name='creator', - field=models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='events_event_creator', blank=True, on_delete=models.CASCADE), + model_name="event", + name="creator", + field=models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="events_event_creator", + blank=True, + on_delete=models.CASCADE, + ), preserve_default=True, ), migrations.AddField( - model_name='event', - name='last_modified_by', - field=models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='events_event_modified', blank=True, on_delete=models.CASCADE), + model_name="event", + name="last_modified_by", + field=models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="events_event_modified", + blank=True, + on_delete=models.CASCADE, + ), preserve_default=True, ), migrations.AddField( - model_name='event', - name='venue', - field=models.ForeignKey(null=True, to='events.EventLocation', related_name='events', blank=True, on_delete=models.CASCADE), + model_name="event", + name="venue", + field=models.ForeignKey( + null=True, to="events.EventLocation", related_name="events", blank=True, on_delete=models.CASCADE + ), preserve_default=True, ), migrations.AddField( - model_name='alarm', - name='event', - field=models.ForeignKey(to='events.Event', on_delete=models.CASCADE), + model_name="alarm", + name="event", + field=models.ForeignKey(to="events.Event", on_delete=models.CASCADE), preserve_default=True, ), migrations.AddField( - model_name='alarm', - name='last_modified_by', - field=models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='events_alarm_modified', blank=True, on_delete=models.CASCADE), + model_name="alarm", + name="last_modified_by", + field=models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="events_alarm_modified", + blank=True, + on_delete=models.CASCADE, + ), preserve_default=True, ), ] diff --git a/events/migrations/0002_auto_20150321_1247.py b/events/migrations/0002_auto_20150321_1247.py index 4b3b4baf5..3daa1e7a6 100644 --- a/events/migrations/0002_auto_20150321_1247.py +++ b/events/migrations/0002_auto_20150321_1247.py @@ -2,21 +2,20 @@ class Migration(migrations.Migration): - dependencies = [ - ('events', '0001_initial'), + ("events", "0001_initial"), ] operations = [ migrations.AddField( - model_name='occurringrule', - name='all_day', + model_name="occurringrule", + name="all_day", field=models.BooleanField(default=False), preserve_default=True, ), migrations.AddField( - model_name='recurringrule', - name='all_day', + model_name="recurringrule", + name="all_day", field=models.BooleanField(default=False), preserve_default=True, ), diff --git a/events/migrations/0003_auto_20150416_1853.py b/events/migrations/0003_auto_20150416_1853.py index 5aa56194a..41b841636 100644 --- a/events/migrations/0003_auto_20150416_1853.py +++ b/events/migrations/0003_auto_20150416_1853.py @@ -2,16 +2,25 @@ class Migration(migrations.Migration): - dependencies = [ - ('events', '0002_auto_20150321_1247'), + ("events", "0002_auto_20150321_1247"), ] operations = [ migrations.AlterField( - model_name='event', - name='description_markup_type', - field=models.CharField(max_length=30, default='restructuredtext', choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')]), + model_name="event", + name="description_markup_type", + field=models.CharField( + max_length=30, + default="restructuredtext", + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + ), preserve_default=True, ), ] diff --git a/events/migrations/0004_auto_20170814_0519.py b/events/migrations/0004_auto_20170814_0519.py index 171852a04..6c99303b7 100644 --- a/events/migrations/0004_auto_20170814_0519.py +++ b/events/migrations/0004_auto_20170814_0519.py @@ -3,20 +3,19 @@ class Migration(migrations.Migration): - dependencies = [ - ('events', '0003_auto_20150416_1853'), + ("events", "0003_auto_20150416_1853"), ] operations = [ migrations.AddField( - model_name='recurringrule', - name='duration_internal', + model_name="recurringrule", + name="duration_internal", field=models.DurationField(default=events.models.duration_default), ), migrations.AlterField( - model_name='recurringrule', - name='duration', - field=models.CharField(default='15 min', max_length=50), + model_name="recurringrule", + name="duration", + field=models.CharField(default="15 min", max_length=50), ), ] diff --git a/events/migrations/0005_auto_20170821_2000.py b/events/migrations/0005_auto_20170821_2000.py index 9d415b232..d23f34599 100644 --- a/events/migrations/0005_auto_20170821_2000.py +++ b/events/migrations/0005_auto_20170821_2000.py @@ -2,15 +2,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('events', '0004_auto_20170814_0519'), + ("events", "0004_auto_20170814_0519"), ] operations = [ migrations.AlterField( - model_name='event', - name='categories', - field=models.ManyToManyField(related_name='events', to='events.EventCategory', blank=True), + model_name="event", + name="categories", + field=models.ManyToManyField(related_name="events", to="events.EventCategory", blank=True), ), ] diff --git a/events/migrations/0006_change_end_date_for_occurring_rules.py b/events/migrations/0006_change_end_date_for_occurring_rules.py index d087a9615..854dd7ef5 100644 --- a/events/migrations/0006_change_end_date_for_occurring_rules.py +++ b/events/migrations/0006_change_end_date_for_occurring_rules.py @@ -7,25 +7,20 @@ def exclude_ending_day(apps, schema_editor): - OccurringRule = apps.get_model('events', 'OccurringRule') + OccurringRule = apps.get_model("events", "OccurringRule") db_alias = schema_editor.connection.alias - OccurringRule.objects.using(db_alias)\ - .filter(all_day=True)\ - .update(dt_end=F('dt_end') - datetime.timedelta(days=1)) + OccurringRule.objects.using(db_alias).filter(all_day=True).update(dt_end=F("dt_end") - datetime.timedelta(days=1)) def include_ending_day(apps, schema_editor): - OccurringRule = apps.get_model('events', 'OccurringRule') + OccurringRule = apps.get_model("events", "OccurringRule") db_alias = schema_editor.connection.alias - OccurringRule.objects.using(db_alias)\ - .filter(all_day=True)\ - .update(dt_end=F('dt_end') + datetime.timedelta(days=1)) + OccurringRule.objects.using(db_alias).filter(all_day=True).update(dt_end=F("dt_end") + datetime.timedelta(days=1)) class Migration(migrations.Migration): - dependencies = [ - ('events', '0005_auto_20170821_2000'), + ("events", "0005_auto_20170821_2000"), ] operations = [ diff --git a/events/migrations/0007_auto_20180705_0352.py b/events/migrations/0007_auto_20180705_0352.py index 3af689184..61305725a 100644 --- a/events/migrations/0007_auto_20180705_0352.py +++ b/events/migrations/0007_auto_20180705_0352.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('events', '0006_change_end_date_for_occurring_rules'), + ("events", "0006_change_end_date_for_occurring_rules"), ] operations = [ migrations.AlterField( - model_name='eventcategory', - name='slug', + model_name="eventcategory", + name="slug", field=models.SlugField(max_length=200, unique=True), ), ] diff --git a/events/migrations/0008_alter_alarm_creator_alter_alarm_last_modified_by_and_more.py b/events/migrations/0008_alter_alarm_creator_alter_alarm_last_modified_by_and_more.py index 371ae3aae..dd050c1c7 100644 --- a/events/migrations/0008_alter_alarm_creator_alter_alarm_last_modified_by_and_more.py +++ b/events/migrations/0008_alter_alarm_creator_alter_alarm_last_modified_by_and_more.py @@ -6,41 +6,76 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('events', '0007_auto_20180705_0352'), + ("events", "0007_auto_20180705_0352"), ] operations = [ migrations.AlterField( - model_name='alarm', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="alarm", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='alarm', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="alarm", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='calendar', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="calendar", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='calendar', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="calendar", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='event', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="event", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='event', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="event", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/events/models.py b/events/models.py index 3334ca326..535762708 100644 --- a/events/models.py +++ b/events/models.py @@ -16,17 +16,20 @@ from markupfield.fields import MarkupField from .utils import ( - minutes_resolution, convert_dt_to_aware, timedelta_nice_repr, timedelta_parse, + minutes_resolution, + convert_dt_to_aware, + timedelta_nice_repr, + timedelta_parse, ) -DEFAULT_MARKUP_TYPE = getattr(settings, 'DEFAULT_MARKUP_TYPE', 'restructuredtext') +DEFAULT_MARKUP_TYPE = getattr(settings, "DEFAULT_MARKUP_TYPE", "restructuredtext") class Calendar(ContentManageable): - url = models.URLField('URL iCal', blank=True, null=True) - rss = models.URLField('RSS Feed', blank=True, null=True) - embed = models.URLField('URL embed', blank=True, null=True) - twitter = models.URLField('Twitter feed', blank=True, null=True) + url = models.URLField("URL iCal", blank=True, null=True) + rss = models.URLField("RSS Feed", blank=True, null=True) + embed = models.URLField("URL embed", blank=True, null=True) + twitter = models.URLField("Twitter feed", blank=True, null=True) name = models.CharField(max_length=100) slug = models.SlugField(unique=True) description = models.CharField(max_length=255, null=True, blank=True) @@ -35,12 +38,13 @@ def __str__(self): return self.name def get_absolute_url(self): - return reverse('events:event_list', kwargs={'calendar_slug': self.slug}) + return reverse("events:event_list", kwargs={"calendar_slug": self.slug}) def import_events(self): if self.url is None: raise ValueError("calendar must have a url field set") from .importer import ICSImporter + importer = ICSImporter(calendar=self) importer.import_events() @@ -48,24 +52,24 @@ def import_events(self): class EventCategory(NameSlugModel): calendar = models.ForeignKey( Calendar, - related_name='categories', + related_name="categories", null=True, blank=True, on_delete=models.CASCADE, ) class Meta: - verbose_name_plural = 'event categories' - ordering = ('name',) + verbose_name_plural = "event categories" + ordering = ("name",) def get_absolute_url(self): - return reverse('events:eventlist_category', kwargs={'calendar_slug': self.calendar.slug, 'slug': self.slug}) + return reverse("events:eventlist_category", kwargs={"calendar_slug": self.calendar.slug, "slug": self.slug}) class EventLocation(models.Model): calendar = models.ForeignKey( Calendar, - related_name='locations', + related_name="locations", null=True, blank=True, on_delete=models.CASCADE, @@ -73,16 +77,16 @@ class EventLocation(models.Model): name = models.CharField(max_length=255) address = models.CharField(blank=True, null=True, max_length=255) - url = models.URLField('URL', blank=True, null=True) + url = models.URLField("URL", blank=True, null=True) class Meta: - ordering = ('name',) + ordering = ("name",) def __str__(self): return self.name def get_absolute_url(self): - return reverse('events:eventlist_location', kwargs={'calendar_slug': self.calendar.slug, 'pk': self.pk}) + return reverse("events:eventlist_location", kwargs={"calendar_slug": self.calendar.slug, "pk": self.pk}) class EventManager(models.Manager): @@ -104,30 +108,30 @@ def until_datetime(self, dt=None): class Event(ContentManageable): uid = models.CharField(max_length=200, null=True, blank=True) title = models.CharField(max_length=200) - calendar = models.ForeignKey(Calendar, related_name='events', on_delete=models.CASCADE) + calendar = models.ForeignKey(Calendar, related_name="events", on_delete=models.CASCADE) description = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE, escape_html=False) venue = models.ForeignKey( EventLocation, - related_name='events', + related_name="events", null=True, blank=True, on_delete=models.CASCADE, ) - categories = models.ManyToManyField(EventCategory, related_name='events', blank=True) + categories = models.ManyToManyField(EventCategory, related_name="events", blank=True) featured = models.BooleanField(default=False, db_index=True) objects = EventManager() class Meta: - ordering = ('-occurring_rule__dt_start',) + ordering = ("-occurring_rule__dt_start",) def __str__(self): return self.title def get_absolute_url(self): - return reverse('events:event_detail', kwargs={'calendar_slug': self.calendar.slug, 'pk': self.pk}) + return reverse("events:event_detail", kwargs={"calendar_slug": self.calendar.slug, "pk": self.pk}) @cached_property def previous_event(self): @@ -230,14 +234,15 @@ class OccurringRule(RuleMixin, models.Model): Shares the same API of `RecurringRule`. """ - event = models.OneToOneField(Event, related_name='occurring_rule', on_delete=models.CASCADE) + + event = models.OneToOneField(Event, related_name="occurring_rule", on_delete=models.CASCADE) dt_start = models.DateTimeField(default=timezone.now) dt_end = models.DateTimeField(default=timezone.now) all_day = models.BooleanField(default=False) def __str__(self): strftime = settings.SHORT_DATETIME_FORMAT - return f'{self.event.title} {date(self.dt_start.strftime, strftime)} - {date(self.dt_end.strftime, strftime)}' + return f"{self.event.title} {date(self.dt_start, strftime)} - {date(self.dt_end, strftime)}" @property def begin(self): @@ -266,25 +271,28 @@ class RecurringRule(RuleMixin, models.Model): Shares the same API of `OccurringRule`. """ + FREQ_CHOICES = ( - (YEARLY, 'year(s)'), - (MONTHLY, 'month(s)'), - (WEEKLY, 'week(s)'), - (DAILY, 'day(s)'), + (YEARLY, "year(s)"), + (MONTHLY, "month(s)"), + (WEEKLY, "week(s)"), + (DAILY, "day(s)"), ) - event = models.ForeignKey(Event, related_name='recurring_rules', on_delete=models.CASCADE) + event = models.ForeignKey(Event, related_name="recurring_rules", on_delete=models.CASCADE) begin = models.DateTimeField(default=timezone.now) finish = models.DateTimeField(default=timezone.now) duration_internal = models.DurationField(default=duration_default) - duration = models.CharField(max_length=50, default='15 min') + duration = models.CharField(max_length=50, default="15 min") interval = models.PositiveSmallIntegerField(default=1) frequency = models.PositiveSmallIntegerField(FREQ_CHOICES, default=WEEKLY) all_day = models.BooleanField(default=False) def __str__(self): - strftime = settings.SHORT_DATETIME_FORMAT - return f'{self.event.title} every {timedelta_nice_repr(self.interval)} since {date(self.dt_start.strftime, strftime)}' + return ( + f"{self.event.title} every {timedelta_nice_repr(self.freq_interval_as_timedelta)} since " + f"{date(self.dt_start, settings.SHORT_DATETIME_FORMAT)}" + ) def to_rrule(self): return rrule( @@ -331,7 +339,7 @@ class Alarm(ContentManageable): trigger = models.PositiveSmallIntegerField(_("hours before the event occurs"), default=24) def __str__(self): - return f'Alarm for {self.event.title} to {self.recipient}' + return f"Alarm for {self.event.title} to {self.recipient}" @property def recipient(self): diff --git a/events/search_indexes.py b/events/search_indexes.py index 9db26a93f..897949186 100644 --- a/events/search_indexes.py +++ b/events/search_indexes.py @@ -7,12 +7,12 @@ class CalendarIndex(indexes.SearchIndex, indexes.Indexable): text = indexes.CharField(document=True, use_template=True) - name = indexes.CharField(model_attr='name') + name = indexes.CharField(model_attr="name") description = indexes.CharField(null=True) path = indexes.CharField() - rss = indexes.CharField(model_attr='rss', null=True) - twitter = indexes.CharField(model_attr='twitter', null=True) - ical_url = indexes.CharField(model_attr='url', null=True) + rss = indexes.CharField(model_attr="rss", null=True) + twitter = indexes.CharField(model_attr="twitter", null=True) + ical_url = indexes.CharField(model_attr="url", null=True) include_template = indexes.CharField() def get_model(self): @@ -29,13 +29,13 @@ def prepare_include_template(self, obj): def prepare(self, obj): data = super().prepare(obj) - data['boost'] = 4 + data["boost"] = 4 return data class EventIndex(indexes.SearchIndex, indexes.Indexable): text = indexes.CharField(document=True, use_template=True) - name = indexes.CharField(model_attr='title') + name = indexes.CharField(model_attr="title") description = indexes.CharField(null=True) venue = indexes.CharField(null=True) path = indexes.CharField() @@ -60,15 +60,15 @@ def prepare_venue(self, obj): return None def prepare(self, obj): - """ Boost events """ + """Boost events""" data = super().prepare(obj) # Reduce boost of past events if obj.is_past: - data['boost'] = 0.9 + data["boost"] = 0.9 elif obj.featured: - data['boost'] = 1.2 + data["boost"] = 1.2 else: - data['boost'] = 1.1 + data["boost"] = 1.1 return data diff --git a/events/templatetags/events.py b/events/templatetags/events.py index fa6bf8063..be56d9991 100644 --- a/events/templatetags/events.py +++ b/events/templatetags/events.py @@ -9,8 +9,7 @@ @register.simple_tag def get_events_upcoming(limit=5, only_featured=False): - qs = Event.objects.for_datetime(timezone.now()).order_by( - 'occurring_rule__dt_start') + qs = Event.objects.for_datetime(timezone.now()).order_by("occurring_rule__dt_start") if only_featured: qs = qs.filter(featured=True) return qs[:limit] diff --git a/events/tests/test_forms.py b/events/tests/test_forms.py index b032ed12d..e3c4b445f 100644 --- a/events/tests/test_forms.py +++ b/events/tests/test_forms.py @@ -6,19 +6,18 @@ class EventFormTests(SimpleTestCase): - def test_valid_form(self): data = { - 'event_name': 'PyConES17', - 'event_type': 'conference', - 'python_focus': 'Country-wide conference', - 'expected_attendees': '500', - 'location': 'Complejo San Francisco, Caceres, Spain', - 'date_from': datetime.datetime(2017, 9, 22), - 'date_to': datetime.datetime(2017, 9, 25), - 'recurrence': 'None', - 'link': 'https://2017.es.pycon.org/en/', - 'description': 'A conference no one can afford to miss', + "event_name": "PyConES17", + "event_type": "conference", + "python_focus": "Country-wide conference", + "expected_attendees": "500", + "location": "Complejo San Francisco, Caceres, Spain", + "date_from": datetime.datetime(2017, 9, 22), + "date_to": datetime.datetime(2017, 9, 25), + "recurrence": "None", + "link": "https://2017.es.pycon.org/en/", + "description": "A conference no one can afford to miss", } form = EventForm(data=data) self.assertTrue(form.is_valid(), form.errors) @@ -26,19 +25,16 @@ def test_valid_form(self): def test_invalid_form(self): data = { - 'event_name': 'PyConES17', - 'event_type': 'conference', - 'python_focus': 'Country-wide conference', - 'expected_attendees': '500', - 'location': 'Complejo San Francisco, Caceres, Spain', - 'date_to': datetime.datetime(2017, 9, 25), - 'recurrence': 'None', - 'link': 'https://2017.es.pycon.org/en/', - 'description': 'A conference no one can afford to miss', + "event_name": "PyConES17", + "event_type": "conference", + "python_focus": "Country-wide conference", + "expected_attendees": "500", + "location": "Complejo San Francisco, Caceres, Spain", + "date_to": datetime.datetime(2017, 9, 25), + "recurrence": "None", + "link": "https://2017.es.pycon.org/en/", + "description": "A conference no one can afford to miss", } form = EventForm(data=data) self.assertFalse(form.is_valid(), form.errors) - self.assertEqual( - form.errors, - {'date_from': ['This field is required.']} - ) + self.assertEqual(form.errors, {"date_from": ["This field is required."]}) diff --git a/events/tests/test_importer.py b/events/tests/test_importer.py index 15a573063..bded219c2 100644 --- a/events/tests/test_importer.py +++ b/events/tests/test_importer.py @@ -7,8 +7,10 @@ from events.models import Calendar, Event CUR_DIR = os.path.dirname(__file__) -EVENTS_CALENDAR = os.path.join(CUR_DIR, 'events.ics') -EVENTS_CALENDAR_URL = 'https://www.google.com/calendar/ical/j7gov1cmnqr9tvg14k621j7t5c@group.calendar.google.com/public/basic.ics' +EVENTS_CALENDAR = os.path.join(CUR_DIR, "events.ics") +EVENTS_CALENDAR_URL = ( + "https://www.google.com/calendar/ical/j7gov1cmnqr9tvg14k621j7t5c@group.calendar.google.com/public/basic.ics" +) class EventsImporterTestCase(TestCase): @@ -16,7 +18,7 @@ class EventsImporterTestCase(TestCase): def setUpClass(cls): # TODO: Use TestCase.setUpTestData() instead in Django 1.8+. super().setUpClass() - cls.calendar = Calendar.objects.create(url=EVENTS_CALENDAR_URL, slug='python-events') + cls.calendar = Calendar.objects.create(url=EVENTS_CALENDAR_URL, slug="python-events") def test_injest(self): importer = ICSImporter(self.calendar) @@ -54,21 +56,14 @@ def test_modified_event(self): """ importer.import_events_from_text(ical) - e = Event.objects.get(uid='8ceqself979pphq4eu7l5e2db8@google.com') + e = Event.objects.get(uid="8ceqself979pphq4eu7l5e2db8@google.com") self.assertEqual(e.calendar.url, EVENTS_CALENDAR_URL) self.assertEqual( - e.description.rendered, - 'PythonCamp Cologne 2016' + e.description.rendered, 'PythonCamp Cologne 2016' ) self.assertTrue(e.next_or_previous_time.all_day) - self.assertEqual( - make_aware(datetime(year=2016, month=4, day=2)), - e.next_or_previous_time.dt_start - ) - self.assertEqual( - make_aware(datetime(year=2016, month=4, day=3)), - e.next_or_previous_time.dt_end - ) + self.assertEqual(make_aware(datetime(year=2016, month=4, day=2)), e.next_or_previous_time.dt_start) + self.assertEqual(make_aware(datetime(year=2016, month=4, day=3)), e.next_or_previous_time.dt_end) ical = """BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN @@ -97,19 +92,13 @@ def test_modified_event(self): """ importer.import_events_from_text(ical) - e2 = Event.objects.get(uid='8ceqself979pphq4eu7l5e2db8@google.com') + e2 = Event.objects.get(uid="8ceqself979pphq4eu7l5e2db8@google.com") self.assertEqual(e.pk, e2.pk) self.assertEqual(e2.calendar.url, EVENTS_CALENDAR_URL) - self.assertEqual(e2.description.rendered, 'Python Istanbul') + self.assertEqual(e2.description.rendered, "Python Istanbul") self.assertTrue(e.next_or_previous_time.all_day) - self.assertEqual( - make_aware(datetime(year=2016, month=4, day=2)), - e.next_or_previous_time.dt_start - ) - self.assertEqual( - make_aware(datetime(year=2016, month=4, day=3)), - e.next_or_previous_time.dt_end - ) + self.assertEqual(make_aware(datetime(year=2016, month=4, day=2)), e.next_or_previous_time.dt_start) + self.assertEqual(make_aware(datetime(year=2016, month=4, day=3)), e.next_or_previous_time.dt_end) def test_import_event_excludes_ending_day_when_all_day_is_true(self): ical = """BEGIN:VCALENDAR @@ -127,17 +116,11 @@ def test_import_event_excludes_ending_day_when_all_day_is_true(self): importer = ICSImporter(self.calendar) importer.import_events_from_text(ical) - all_day_event = Event.objects.get(uid='pythoncalendartest@python.org') + all_day_event = Event.objects.get(uid="pythoncalendartest@python.org") self.assertTrue(all_day_event.next_or_previous_time.all_day) self.assertFalse(all_day_event.next_or_previous_time.single_day) - self.assertEqual( - make_aware(datetime(year=2015, month=3, day=28)), - all_day_event.next_or_previous_time.dt_start - ) - self.assertEqual( - make_aware(datetime(year=2015, month=3, day=29)), - all_day_event.next_or_previous_time.dt_end - ) + self.assertEqual(make_aware(datetime(year=2015, month=3, day=28)), all_day_event.next_or_previous_time.dt_start) + self.assertEqual(make_aware(datetime(year=2015, month=3, day=29)), all_day_event.next_or_previous_time.dt_end) def test_import_event_does_not_exclude_ending_day_when_all_day_is_false(self): ical = """BEGIN:VCALENDAR @@ -156,14 +139,13 @@ def test_import_event_does_not_exclude_ending_day_when_all_day_is_false(self): importer = ICSImporter(self.calendar) importer.import_events_from_text(ical) - single_day_event = Event.objects.get(uid='pythoncalendartestsingleday@python.org') + single_day_event = Event.objects.get(uid="pythoncalendartestsingleday@python.org") self.assertFalse(single_day_event.next_or_previous_time.all_day) self.assertTrue(single_day_event.next_or_previous_time.single_day) self.assertEqual( - make_aware(datetime(year=2013, month=8, day=2, hour=20)), - single_day_event.next_or_previous_time.dt_start + make_aware(datetime(year=2013, month=8, day=2, hour=20)), single_day_event.next_or_previous_time.dt_start ) self.assertEqual( make_aware(datetime(year=2013, month=8, day=2, hour=20, minute=30)), - single_day_event.next_or_previous_time.dt_end + single_day_event.next_or_previous_time.dt_end, ) diff --git a/events/tests/test_models.py b/events/tests/test_models.py index 0f3bafe76..819cfeee9 100644 --- a/events/tests/test_models.py +++ b/events/tests/test_models.py @@ -12,9 +12,9 @@ class EventsModelsTests(TestCase): def setUp(self): - self.user = get_user_model().objects.create_user(username='username', password='password') - self.calendar = Calendar.objects.create(creator=self.user, slug='test-calendar') - self.event = Event.objects.create(title='event', creator=self.user, calendar=self.calendar) + self.user = get_user_model().objects.create_user(username="username", password="password") + self.calendar = Calendar.objects.create(creator=self.user, slug="test-calendar") + self.event = Event.objects.create(title="event", creator=self.user, calendar=self.calendar) def test_occurring_event(self): now = seconds_resolution(timezone.now()) @@ -62,7 +62,6 @@ def test_recurring_event(self): self.assertEqual(self.event.next_time.dt_start, recurring_time_dtstart) self.assertTrue(rt.valid_dt_end()) - rt.begin = now - datetime.timedelta(days=5) rt.finish = now - datetime.timedelta(days=3) rt.save() @@ -84,12 +83,7 @@ def test_rrule(self): ) self.assertEqual(rt.freq_interval_as_timedelta, datetime.timedelta(days=7)) - dateutil_rrule = rrule( - WEEKLY, - interval=1, - dtstart=recurring_time_dtstart, - until=recurring_time_dtend - ) + dateutil_rrule = rrule(WEEKLY, interval=1, dtstart=recurring_time_dtstart, until=recurring_time_dtend) self.assertEqual(rt.to_rrule().after(now), dateutil_rrule.after(now)) self.assertEqual(rt.dt_start, rt.to_rrule().after(now)) diff --git a/events/tests/test_utils.py b/events/tests/test_utils.py index 7e49223ec..dd8eefd2b 100644 --- a/events/tests/test_utils.py +++ b/events/tests/test_utils.py @@ -4,7 +4,10 @@ from django.test import TestCase from ..utils import ( - seconds_resolution, minutes_resolution, timedelta_nice_repr, timedelta_parse, + seconds_resolution, + minutes_resolution, + timedelta_nice_repr, + timedelta_parse, ) @@ -25,72 +28,67 @@ def test_minutes_resolution(self): def test_timedelta_nice_repr(self): tests = [ - (dict(days=1, hours=2, minutes=3, seconds=4), (), - '1 day, 2 hours, 3 minutes, 4 seconds'), - (dict(days=1, seconds=1), ('minimal',), '1d, 1s'), - (dict(days=1), (), '1 day'), - (dict(days=0), (), '0 seconds'), - (dict(seconds=1), (), '1 second'), - (dict(seconds=10), (), '10 seconds'), - (dict(seconds=30), (), '30 seconds'), - (dict(seconds=60), (), '1 minute'), - (dict(seconds=150), (), '2 minutes, 30 seconds'), - (dict(seconds=1800), (), '30 minutes'), - (dict(seconds=3600), (), '1 hour'), - (dict(seconds=3601), (), '1 hour, 1 second'), - (dict(seconds=3601), (), '1 hour, 1 second'), - (dict(seconds=19800), (), '5 hours, 30 minutes'), - (dict(seconds=91800), (), '1 day, 1 hour, 30 minutes'), - (dict(seconds=302400), (), '3 days, 12 hours'), - (dict(seconds=0), ('minimal',), '0s'), - (dict(seconds=0), ('short',), '0 sec'), - (dict(seconds=0), ('long',), '0 seconds'), + (dict(days=1, hours=2, minutes=3, seconds=4), (), "1 day, 2 hours, 3 minutes, 4 seconds"), + (dict(days=1, seconds=1), ("minimal",), "1d, 1s"), + (dict(days=1), (), "1 day"), + (dict(days=0), (), "0 seconds"), + (dict(seconds=1), (), "1 second"), + (dict(seconds=10), (), "10 seconds"), + (dict(seconds=30), (), "30 seconds"), + (dict(seconds=60), (), "1 minute"), + (dict(seconds=150), (), "2 minutes, 30 seconds"), + (dict(seconds=1800), (), "30 minutes"), + (dict(seconds=3600), (), "1 hour"), + (dict(seconds=3601), (), "1 hour, 1 second"), + (dict(seconds=3601), (), "1 hour, 1 second"), + (dict(seconds=19800), (), "5 hours, 30 minutes"), + (dict(seconds=91800), (), "1 day, 1 hour, 30 minutes"), + (dict(seconds=302400), (), "3 days, 12 hours"), + (dict(seconds=0), ("minimal",), "0s"), + (dict(seconds=0), ("short",), "0 sec"), + (dict(seconds=0), ("long",), "0 seconds"), ] for timedelta, arguments, expected in tests: with self.subTest(timedelta=timedelta, arguments=arguments): - self.assertEqual( - timedelta_nice_repr(datetime.timedelta(**timedelta), *arguments), - expected - ) - self.assertRaises(TypeError, timedelta_nice_repr, '') + self.assertEqual(timedelta_nice_repr(datetime.timedelta(**timedelta), *arguments), expected) + self.assertRaises(TypeError, timedelta_nice_repr, "") def test_timedelta_parse(self): tests = [ - ('1 day', datetime.timedelta(1)), - ('2 days', datetime.timedelta(2)), - ('1 d', datetime.timedelta(1)), - ('1 hour', datetime.timedelta(0, 3600)), - ('1 hours', datetime.timedelta(0, 3600)), - ('1 hr', datetime.timedelta(0, 3600)), - ('1 hrs', datetime.timedelta(0, 3600)), - ('1h', datetime.timedelta(0, 3600)), - ('1wk', datetime.timedelta(7)), - ('1 week', datetime.timedelta(7)), - ('1 weeks', datetime.timedelta(7)), - ('2 weeks', datetime.timedelta(14)), - ('1 sec', datetime.timedelta(0, 1)), - ('1 secs', datetime.timedelta(0, 1)), - ('1 s', datetime.timedelta(0, 1)), - ('1 second', datetime.timedelta(0, 1)), - ('1 seconds', datetime.timedelta(0, 1)), - ('1 minute', datetime.timedelta(0, 60)), - ('1 min', datetime.timedelta(0, 60)), - ('1 m', datetime.timedelta(0, 60)), - ('1 minutes', datetime.timedelta(0, 60)), - ('1 mins', datetime.timedelta(0, 60)), - ('1.5 days', datetime.timedelta(1, 43200)), - ('3 weeks', datetime.timedelta(21)), - ('4.2 hours', datetime.timedelta(0, 15120)), - ('.5 hours', datetime.timedelta(0, 1800)), - ('1 hour, 5 mins', datetime.timedelta(0, 3900)), - ('-2 days', datetime.timedelta(-2)), - ('-1 day 0:00:01', datetime.timedelta(-1, 1)), - ('-1 day, -1:01:01', datetime.timedelta(-2, 82739)), - ('-1 weeks, 2 days, -3 hours, 4 minutes, -5 seconds', - datetime.timedelta(-5, 11045)), - ('0 seconds', datetime.timedelta(0)), - ('0 days', datetime.timedelta(0)), - ('0 weeks', datetime.timedelta(0)), + ("1 day", datetime.timedelta(1)), + ("2 days", datetime.timedelta(2)), + ("1 d", datetime.timedelta(1)), + ("1 hour", datetime.timedelta(0, 3600)), + ("1 hours", datetime.timedelta(0, 3600)), + ("1 hr", datetime.timedelta(0, 3600)), + ("1 hrs", datetime.timedelta(0, 3600)), + ("1h", datetime.timedelta(0, 3600)), + ("1wk", datetime.timedelta(7)), + ("1 week", datetime.timedelta(7)), + ("1 weeks", datetime.timedelta(7)), + ("2 weeks", datetime.timedelta(14)), + ("1 sec", datetime.timedelta(0, 1)), + ("1 secs", datetime.timedelta(0, 1)), + ("1 s", datetime.timedelta(0, 1)), + ("1 second", datetime.timedelta(0, 1)), + ("1 seconds", datetime.timedelta(0, 1)), + ("1 minute", datetime.timedelta(0, 60)), + ("1 min", datetime.timedelta(0, 60)), + ("1 m", datetime.timedelta(0, 60)), + ("1 minutes", datetime.timedelta(0, 60)), + ("1 mins", datetime.timedelta(0, 60)), + ("1.5 days", datetime.timedelta(1, 43200)), + ("3 weeks", datetime.timedelta(21)), + ("4.2 hours", datetime.timedelta(0, 15120)), + (".5 hours", datetime.timedelta(0, 1800)), + ("1 hour, 5 mins", datetime.timedelta(0, 3900)), + ("-2 days", datetime.timedelta(-2)), + ("-1 day 0:00:01", datetime.timedelta(-1, 1)), + ("-1 day, -1:01:01", datetime.timedelta(-2, 82739)), + ("-1 weeks, 2 days, -3 hours, 4 minutes, -5 seconds", datetime.timedelta(-5, 11045)), + ("0 seconds", datetime.timedelta(0)), + ("0 days", datetime.timedelta(0)), + ("0 weeks", datetime.timedelta(0)), ] for string, timedelta in tests: with self.subTest(string=string): @@ -98,13 +96,13 @@ def test_timedelta_parse(self): def test_timedelta_parse_invalid(self): tests = [ - ('2 ws', TypeError), - ('2 ds', TypeError), - ('2 hs', TypeError), - ('2 ms', TypeError), - ('2 aa', TypeError), - ('', TypeError), - (' hours', TypeError), + ("2 ws", TypeError), + ("2 ds", TypeError), + ("2 hs", TypeError), + ("2 ms", TypeError), + ("2 aa", TypeError), + ("", TypeError), + (" hours", TypeError), ] for string, exception in tests: with self.subTest(string=string): diff --git a/events/tests/test_views.py b/events/tests/test_views.py index 691817036..f80c1a56c 100644 --- a/events/tests/test_views.py +++ b/events/tests/test_views.py @@ -14,10 +14,10 @@ class EventsViewsTests(TestCase): @classmethod def setUpTestData(cls): - cls.user = get_user_model().objects.create_user(username='username', password='password') + cls.user = get_user_model().objects.create_user(username="username", password="password") cls.calendar = Calendar.objects.create(creator=cls.user, slug="test-calendar") cls.event = Event.objects.create(creator=cls.user, calendar=cls.calendar) - cls.event_past = Event.objects.create(title='Past Event', creator=cls.user, calendar=cls.calendar) + cls.event_past = Event.objects.create(title="Past Event", creator=cls.user, calendar=cls.calendar) cls.now = timezone.now() @@ -36,118 +36,107 @@ def setUpTestData(cls): ) def test_events_homepage(self): - url = reverse('events:events') + url = reverse("events:events") response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 1) + self.assertEqual(len(response.context["object_list"]), 1) def test_calendar_list(self): calendars_count = Calendar.objects.count() - url = reverse('events:calendar_list') + url = reverse("events:calendar_list") response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), calendars_count) + self.assertEqual(len(response.context["object_list"]), calendars_count) def test_event_list(self): - url = reverse('events:event_list', kwargs={"calendar_slug": self.calendar.slug}) + url = reverse("events:event_list", kwargs={"calendar_slug": self.calendar.slug}) response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 1) + self.assertEqual(len(response.context["object_list"]), 1) - url = reverse('events:event_list_past', kwargs={"calendar_slug": 'unexisting'}) + url = reverse("events:event_list_past", kwargs={"calendar_slug": "unexisting"}) response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_event_list_past(self): - url = reverse('events:event_list_past', kwargs={"calendar_slug": self.calendar.slug}) + url = reverse("events:event_list_past", kwargs={"calendar_slug": self.calendar.slug}) response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 1) + self.assertEqual(len(response.context["object_list"]), 1) def test_event_list_category(self): - category = EventCategory.objects.create( - name='Sprints', - slug='sprints', - calendar=self.calendar - ) + category = EventCategory.objects.create(name="Sprints", slug="sprints", calendar=self.calendar) self.event.categories.add(category) - url = reverse('events:eventlist_category', kwargs={'calendar_slug': self.calendar.slug, 'slug': category.slug}) + url = reverse("events:eventlist_category", kwargs={"calendar_slug": self.calendar.slug, "slug": category.slug}) response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(response.context['object'], category) - self.assertEqual(len(response.context['object_list']), 1) - self.assertEqual(len(response.context['event_categories']), 1) + self.assertEqual(response.context["object"], category) + self.assertEqual(len(response.context["object_list"]), 1) + self.assertEqual(len(response.context["event_categories"]), 1) def test_event_list_location(self): - venue = EventLocation.objects.create( - name='PSF HQ', - calendar=self.calendar - ) + venue = EventLocation.objects.create(name="PSF HQ", calendar=self.calendar) self.event.venue = venue self.event.save() - url = reverse('events:eventlist_location', kwargs={'calendar_slug': self.calendar.slug, 'pk': venue.pk}) + url = reverse("events:eventlist_location", kwargs={"calendar_slug": self.calendar.slug, "pk": venue.pk}) response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(response.context['object'], venue) - self.assertEqual(len(response.context['object_list']), 1) - self.assertEqual(len(response.context['event_locations']), 1) + self.assertEqual(response.context["object"], venue) + self.assertEqual(len(response.context["object_list"]), 1) + self.assertEqual(len(response.context["event_locations"]), 1) - url = reverse('events:eventlist_location', kwargs={'calendar_slug': self.calendar.slug, 'pk': 1234}) + url = reverse("events:eventlist_location", kwargs={"calendar_slug": self.calendar.slug, "pk": 1234}) response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_event_list_date(self): dt = self.now - datetime.timedelta(days=2) - url = reverse('events:eventlist_date', kwargs={ - 'calendar_slug': self.calendar.slug, - 'year': dt.year, - 'month': "%02d" % dt.month, - 'day': "%02d" % dt.day, - }) + url = reverse( + "events:eventlist_date", + kwargs={ + "calendar_slug": self.calendar.slug, + "year": dt.year, + "month": "%02d" % dt.month, + "day": "%02d" % dt.day, + }, + ) response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(response.context['object'], dt.date()) - self.assertEqual(len(response.context['object_list']), 2) + self.assertEqual(response.context["object"], dt.date()) + self.assertEqual(len(response.context["object_list"]), 2) def test_eventlocation_list(self): - venue = EventLocation.objects.create( - name='PSF HQ', - calendar=self.calendar - ) + venue = EventLocation.objects.create(name="PSF HQ", calendar=self.calendar) self.event.venue = venue self.event.save() - url = reverse('events:eventlocation_list', kwargs={'calendar_slug': self.calendar.slug}) + url = reverse("events:eventlocation_list", kwargs={"calendar_slug": self.calendar.slug}) response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertIn(venue, response.context['object_list']) + self.assertIn(venue, response.context["object_list"]) def test_eventcategory_list(self): - category = EventCategory.objects.create( - name='Sprints', - slug='sprints', - calendar=self.calendar - ) + category = EventCategory.objects.create(name="Sprints", slug="sprints", calendar=self.calendar) self.event.categories.add(category) - url = reverse('events:eventcategory_list', kwargs={'calendar_slug': self.calendar.slug}) + url = reverse("events:eventcategory_list", kwargs={"calendar_slug": self.calendar.slug}) response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertIn(category, response.context['object_list']) + self.assertIn(category, response.context["object_list"]) def test_event_detail(self): - url = reverse('events:event_detail', kwargs={'calendar_slug': self.calendar.slug, 'pk': self.event.pk}) + url = reverse("events:event_detail", kwargs={"calendar_slug": self.calendar.slug, "pk": self.event.pk}) response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(self.event, response.context['object']) + self.assertEqual(self.event, response.context["object"]) def test_upcoming_tag(self): self.assertEqual(len(get_events_upcoming()), 1) @@ -159,31 +148,31 @@ def test_upcoming_tag(self): class EventSubmitTests(TestCase): - event_submit_url = reverse_lazy('events:event_submit') + event_submit_url = reverse_lazy("events:event_submit") @classmethod def setUpTestData(cls): - cls.user = UserFactory(password='password') + cls.user = UserFactory(password="password") cls.post_data = { - 'event_name': 'PyConES17', - 'event_type': 'conference', - 'python_focus': 'Country-wide conference', - 'expected_attendees': '500', - 'location': 'Complejo San Francisco, Caceres, Spain', - 'date_from': '2017-9-22', - 'date_to': '2017-9-24', - 'recurrence': 'None', - 'link': 'https://2017.es.pycon.org/en/', - 'description': 'A conference no one can afford to miss', + "event_name": "PyConES17", + "event_type": "conference", + "python_focus": "Country-wide conference", + "expected_attendees": "500", + "location": "Complejo San Francisco, Caceres, Spain", + "date_from": "2017-9-22", + "date_to": "2017-9-24", + "recurrence": "None", + "link": "https://2017.es.pycon.org/en/", + "description": "A conference no one can afford to miss", } def user_login(self): - self.client.login(username=self.user.username, password='password') + self.client.login(username=self.user.username, password="password") def test_submit_not_logged_in_is_redirected(self): response = self.client.post(self.event_submit_url, self.post_data) self.assertEqual(response.status_code, 302) - self.assertRedirects(response, '/accounts/login/?next=/events/submit/') + self.assertRedirects(response, "/accounts/login/?next=/events/submit/") def test_submit_without_data_is_rejected(self): self.user_login() @@ -197,21 +186,16 @@ def test_submit_success_sends_email(self): self.user_login() response = self.client.post(self.event_submit_url, self.post_data) self.assertEqual(response.status_code, 302) - self.assertRedirects(response, reverse('events:event_thanks')) + self.assertRedirects(response, reverse("events:event_thanks")) self.assertEqual(len(mail.outbox), 1) - self.assertEqual( - mail.outbox[0].subject, - 'New event submission: "{}"'.format(self.post_data['event_name']) - ) + self.assertEqual(mail.outbox[0].subject, 'New event submission: "{}"'.format(self.post_data["event_name"])) def test_badheadererror(self): self.user_login() post_data = self.post_data.copy() - post_data['event_name'] = 'invalid\ntitle' - response = self.client.post( - self.event_submit_url, post_data, follow=True - ) + post_data["event_name"] = "invalid\ntitle" + response = self.client.post(self.event_submit_url, post_data, follow=True) self.assertEqual(response.status_code, 200) - messages = list(response.context['messages']) + messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) - self.assertEqual(messages[0].message, 'Invalid header found.') + self.assertEqual(messages[0].message, "Invalid header found.") diff --git a/events/urls.py b/events/urls.py index 8bb2d0135..d01d6c339 100644 --- a/events/urls.py +++ b/events/urls.py @@ -3,18 +3,24 @@ from . import views from django.urls import path, re_path -app_name = 'events' +app_name = "events" urlpatterns = [ - path('calendars/', views.CalendarList.as_view(), name='calendar_list'), - path('submit/', views.EventSubmit.as_view(), name='event_submit'), - path('submit/thanks/', TemplateView.as_view(template_name='events/event_form_thanks.html'), name='event_thanks'), - path('/categories//', views.EventListByCategory.as_view(), name='eventlist_category'), - path('/categories/', views.EventCategoryList.as_view(), name='eventcategory_list'), - path('/locations//', views.EventListByLocation.as_view(), name='eventlist_location'), - path('/locations/', views.EventLocationList.as_view(), name='eventlocation_list'), - re_path(r'(?P[-a-zA-Z0-9_]+)/date/(?P\d{4})/(?P\d{2})/(?P\d{2})/$', views.EventListByDate.as_view(), name='eventlist_date'), - path('//', views.EventDetail.as_view(), name='event_detail'), - path('/past/', views.PastEventList.as_view(), name='event_list_past'), - path('/', views.EventList.as_view(), name='event_list'), - path('', views.EventHomepage.as_view(), name='events'), + path("calendars/", views.CalendarList.as_view(), name="calendar_list"), + path("submit/", views.EventSubmit.as_view(), name="event_submit"), + path("submit/thanks/", TemplateView.as_view(template_name="events/event_form_thanks.html"), name="event_thanks"), + path( + "/categories//", views.EventListByCategory.as_view(), name="eventlist_category" + ), + path("/categories/", views.EventCategoryList.as_view(), name="eventcategory_list"), + path("/locations//", views.EventListByLocation.as_view(), name="eventlist_location"), + path("/locations/", views.EventLocationList.as_view(), name="eventlocation_list"), + re_path( + r"(?P[-a-zA-Z0-9_]+)/date/(?P\d{4})/(?P\d{2})/(?P\d{2})/$", + views.EventListByDate.as_view(), + name="eventlist_date", + ), + path("//", views.EventDetail.as_view(), name="event_detail"), + path("/past/", views.PastEventList.as_view(), name="event_list_past"), + path("/", views.EventList.as_view(), name="event_list"), + path("", views.EventHomepage.as_view(), name="events"), ] diff --git a/events/utils.py b/events/utils.py index a3801d4a6..d8bce5a36 100644 --- a/events/utils.py +++ b/events/utils.py @@ -37,7 +37,7 @@ def convert_dt_to_aware(dt): return dt -def timedelta_nice_repr(timedelta, display='long', sep=', '): +def timedelta_nice_repr(timedelta, display="long", sep=", "): """ Turns a datetime.timedelta object into a nice string repr. @@ -47,42 +47,42 @@ def timedelta_nice_repr(timedelta, display='long', sep=', '): 'sql' and 'iso8601' support have been removed. """ if not isinstance(timedelta, datetime.timedelta): - raise TypeError('First argument must be a timedelta.') + raise TypeError("First argument must be a timedelta.") result = [] weeks = int(timedelta.days / 7) days = timedelta.days % 7 hours = int(timedelta.seconds / 3600) minutes = int((timedelta.seconds % 3600) / 60) seconds = timedelta.seconds % 60 - if display == 'minimal': - words = ['w', 'd', 'h', 'm', 's'] - elif display == 'short': - words = [' wks', ' days', ' hrs', ' min', ' sec'] - elif display == 'long': - words = [' weeks', ' days', ' hours', ' minutes', ' seconds'] + if display == "minimal": + words = ["w", "d", "h", "m", "s"] + elif display == "short": + words = [" wks", " days", " hrs", " min", " sec"] + elif display == "long": + words = [" weeks", " days", " hours", " minutes", " seconds"] else: # Use django template-style formatting. # Valid values are d, g, G, h, H, i, s. - return re.sub(r'([dgGhHis])', lambda x: '%%(%s)s' % x.group(), display) % { - 'd': days, - 'g': hours, - 'G': hours if hours > 9 else '0%s' % hours, - 'h': hours, - 'H': hours if hours > 9 else '0%s' % hours, - 'i': minutes if minutes > 9 else '0%s' % minutes, - 's': seconds if seconds > 9 else '0%s' % seconds + return re.sub(r"([dgGhHis])", lambda x: "%%(%s)s" % x.group(), display) % { + "d": days, + "g": hours, + "G": hours if hours > 9 else "0%s" % hours, + "h": hours, + "H": hours if hours > 9 else "0%s" % hours, + "i": minutes if minutes > 9 else "0%s" % minutes, + "s": seconds if seconds > 9 else "0%s" % seconds, } values = [weeks, days, hours, minutes, seconds] for i in range(len(values)): if values[i]: if values[i] == 1 and len(words[i]) > 1: - result.append('%i%s' % (values[i], words[i].rstrip('s'))) + result.append("%i%s" % (values[i], words[i].rstrip("s"))) else: - result.append('%i%s' % (values[i], words[i])) + result.append("%i%s" % (values[i], words[i])) # Values with less than one second, which are considered zeroes. if len(result) == 0: # Display as 0 of the smallest unit. - result.append('0%s' % (words[-1])) + result.append("0%s" % (words[-1])) return sep.join(result) @@ -94,31 +94,31 @@ def timedelta_parse(string): """ string = string.strip() if not string: - raise TypeError(f'{string!r} is not a valid time interval') + raise TypeError(f"{string!r} is not a valid time interval") # This is the format we get from sometimes PostgreSQL, sqlite, # and from serialization. d = re.match( - r'^((?P[-+]?\d+) days?,? )?(?P[-+]?)(?P\d+):' - r'(?P\d+)(:(?P\d+(\.\d+)?))?$', - string + r"^((?P[-+]?\d+) days?,? )?(?P[-+]?)(?P\d+):" + r"(?P\d+)(:(?P\d+(\.\d+)?))?$", + string, ) if d: d = d.groupdict(0) - if d['sign'] == '-': - for k in 'hours', 'minutes', 'seconds': - d[k] = '-' + d[k] - d.pop('sign', None) + if d["sign"] == "-": + for k in "hours", "minutes", "seconds": + d[k] = "-" + d[k] + d.pop("sign", None) else: # This is the more flexible format. d = re.match( - r'^((?P-?((\d*\.\d+)|\d+))\W*w((ee)?(k(s)?)?)(,)?\W*)?' - r'((?P-?((\d*\.\d+)|\d+))\W*d(ay(s)?)?(,)?\W*)?' - r'((?P-?((\d*\.\d+)|\d+))\W*h(ou)?(r(s)?)?(,)?\W*)?' - r'((?P-?((\d*\.\d+)|\d+))\W*m(in(ute)?(s)?)?(,)?\W*)?' - r'((?P-?((\d*\.\d+)|\d+))\W*s(ec(ond)?(s)?)?)?\W*$', - string + r"^((?P-?((\d*\.\d+)|\d+))\W*w((ee)?(k(s)?)?)(,)?\W*)?" + r"((?P-?((\d*\.\d+)|\d+))\W*d(ay(s)?)?(,)?\W*)?" + r"((?P-?((\d*\.\d+)|\d+))\W*h(ou)?(r(s)?)?(,)?\W*)?" + r"((?P-?((\d*\.\d+)|\d+))\W*m(in(ute)?(s)?)?(,)?\W*)?" + r"((?P-?((\d*\.\d+)|\d+))\W*s(ec(ond)?(s)?)?)?\W*$", + string, ) if not d: - raise TypeError(f'{string!r} is not a valid time interval') + raise TypeError(f"{string!r} is not a valid time interval") d = d.groupdict(0) return datetime.timedelta(**{k: float(v) for k, v in d.items()}) diff --git a/events/views.py b/events/views.py index 2490626e3..a227a30d5 100644 --- a/events/views.py +++ b/events/views.py @@ -28,22 +28,23 @@ def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) featured_events = self.get_queryset().filter(featured=True) try: - context['featured'] = featured_events[0] + context["featured"] = featured_events[0] except IndexError: pass - context['event_categories'] = EventCategory.objects.all()[:10] - context['event_locations'] = EventLocation.objects.all()[:10] - context['object'] = self.get_object() + context["event_categories"] = EventCategory.objects.all()[:10] + context["event_locations"] = EventLocation.objects.all()[:10] + context["object"] = self.get_object() return context class EventHomepage(ListView): - """ Main Event Landing Page """ - template_name = 'events/event_list.html' + """Main Event Landing Page""" + + template_name = "events/event_list.html" def get_queryset(self): - return Event.objects.for_datetime(timezone.now()).order_by('occurring_rule__dt_start') + return Event.objects.for_datetime(timezone.now()).order_by("occurring_rule__dt_start") class EventDetail(DetailView): @@ -54,63 +55,70 @@ def get_queryset(self): def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) - if data['object'].next_time: - dt = data['object'].next_time.dt_start - data.update({ - 'next_7': dt + datetime.timedelta(days=7), - 'next_30': dt + datetime.timedelta(days=30), - 'next_90': dt + datetime.timedelta(days=90), - 'next_365': dt + datetime.timedelta(days=365), - }) + if data["object"].next_time: + dt = data["object"].next_time.dt_start + data.update( + { + "next_7": dt + datetime.timedelta(days=7), + "next_30": dt + datetime.timedelta(days=30), + "next_90": dt + datetime.timedelta(days=90), + "next_365": dt + datetime.timedelta(days=365), + } + ) return data class EventList(EventListBase): - def get_queryset(self): - return Event.objects.for_datetime(timezone.now()).filter(calendar__slug=self.kwargs['calendar_slug']).order_by('occurring_rule__dt_start') + return ( + Event.objects.for_datetime(timezone.now()) + .filter(calendar__slug=self.kwargs["calendar_slug"]) + .order_by("occurring_rule__dt_start") + ) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['events_today'] = Event.objects.until_datetime(timezone.now()).filter(calendar__slug=self.kwargs['calendar_slug'])[:2] - context['calendar'] = get_object_or_404(Calendar, slug=self.kwargs['calendar_slug']) + context["events_today"] = Event.objects.until_datetime(timezone.now()).filter( + calendar__slug=self.kwargs["calendar_slug"] + )[:2] + context["calendar"] = get_object_or_404(Calendar, slug=self.kwargs["calendar_slug"]) return context class PastEventList(EventList): - template_name = 'events/event_list_past.html' + template_name = "events/event_list_past.html" def get_queryset(self): - return Event.objects.until_datetime(timezone.now()).filter(calendar__slug=self.kwargs['calendar_slug']) + return Event.objects.until_datetime(timezone.now()).filter(calendar__slug=self.kwargs["calendar_slug"]) class EventListByDate(EventList): def get_object(self): - year = int(self.kwargs['year']) - month = int(self.kwargs['month']) - day = int(self.kwargs['day']) + year = int(self.kwargs["year"]) + month = int(self.kwargs["month"]) + day = int(self.kwargs["day"]) return datetime.date(year, month, day) def get_queryset(self): - return Event.objects.for_datetime(self.get_object()).filter(calendar__slug=self.kwargs['calendar_slug']) + return Event.objects.for_datetime(self.get_object()).filter(calendar__slug=self.kwargs["calendar_slug"]) class EventListByCategory(EventList): def get_object(self, queryset=None): - return get_object_or_404(EventCategory, calendar__slug=self.kwargs['calendar_slug'], slug=self.kwargs['slug']) + return get_object_or_404(EventCategory, calendar__slug=self.kwargs["calendar_slug"], slug=self.kwargs["slug"]) def get_queryset(self): qs = super().get_queryset() - return qs.filter(categories__slug=self.kwargs['slug']) + return qs.filter(categories__slug=self.kwargs["slug"]) class EventListByLocation(EventList): def get_object(self, queryset=None): - return get_object_or_404(EventLocation, calendar__slug=self.kwargs['calendar_slug'], pk=self.kwargs['pk']) + return get_object_or_404(EventLocation, calendar__slug=self.kwargs["calendar_slug"], pk=self.kwargs["pk"]) def get_queryset(self): qs = super().get_queryset() - return qs.filter(venue__pk=self.kwargs['pk']) + return qs.filter(venue__pk=self.kwargs["pk"]) class EventCategoryList(ListView): @@ -118,10 +126,10 @@ class EventCategoryList(ListView): paginate_by = 30 def get_queryset(self): - return self.model.objects.filter(calendar__slug=self.kwargs['calendar_slug']) + return self.model.objects.filter(calendar__slug=self.kwargs["calendar_slug"]) def get_context_data(self, **kwargs): - kwargs['event_categories'] = self.get_queryset()[:10] + kwargs["event_categories"] = self.get_queryset()[:10] return super().get_context_data(**kwargs) @@ -131,18 +139,18 @@ class EventLocationList(ListView): paginate_by = 30 def get_queryset(self): - return self.model.objects.filter(calendar__slug=self.kwargs['calendar_slug']) + return self.model.objects.filter(calendar__slug=self.kwargs["calendar_slug"]) class EventSubmit(LoginRequiredMixin, FormView): - template_name = 'events/event_form.html' + template_name = "events/event_form.html" form_class = EventForm - success_url = reverse_lazy('events:event_thanks') + success_url = reverse_lazy("events:event_thanks") def form_valid(self, form): try: form.send_email(self.request.user) except BadHeaderError: - messages.add_message(self.request, messages.ERROR, 'Invalid header found.') - return redirect('events:event_submit') + messages.add_message(self.request, messages.ERROR, "Invalid header found.") + return redirect("events:event_submit") return super().form_valid(form) diff --git a/fastly/utils.py b/fastly/utils.py index 42637aeb2..9b9f09a33 100644 --- a/fastly/utils.py +++ b/fastly/utils.py @@ -10,12 +10,12 @@ def purge_url(path): if settings.DEBUG: return - api_key = getattr(settings, 'FASTLY_API_KEY', None) + api_key = getattr(settings, "FASTLY_API_KEY", None) if api_key: response = requests.request( - 'PURGE', - f'https://www.python.org{path}', - headers={'Fastly-Key': api_key}, + "PURGE", + f"https://www.python.org{path}", + headers={"Fastly-Key": api_key}, ) return response diff --git a/jobs/admin.py b/jobs/admin.py index 7c811e95e..4c658927d 100644 --- a/jobs/admin.py +++ b/jobs/admin.py @@ -6,29 +6,29 @@ @admin.register(Job) class JobAdmin(ContentManageableModelAdmin): - date_hierarchy = 'created' - filter_horizontal = ['job_types'] - list_display = ['__str__', 'job_title', 'status', 'company_name'] - list_filter = ['status', 'telecommuting'] - raw_id_fields = ['category', 'submitted_by'] - search_fields = ['id', 'job_title'] + date_hierarchy = "created" + filter_horizontal = ["job_types"] + list_display = ["__str__", "job_title", "status", "company_name"] + list_filter = ["status", "telecommuting"] + raw_id_fields = ["category", "submitted_by"] + search_fields = ["id", "job_title"] @admin.register(JobType) class JobTypeAdmin(NameSlugAdmin): - list_display = ['__str__', 'active'] - list_filter = ['active'] - ordering = ('-active', 'name') + list_display = ["__str__", "active"] + list_filter = ["active"] + ordering = ("-active", "name") @admin.register(JobCategory) class JobCategoryAdmin(NameSlugAdmin): - list_display = ['__str__', 'active'] - list_filter = ['active'] - ordering = ('-active', 'name') + list_display = ["__str__", "active"] + list_filter = ["active"] + ordering = ("-active", "name") @admin.register(JobReviewComment) class JobReviewCommentAdmin(ContentManageableModelAdmin): - list_display = ['__str__', 'job'] - ordering = ('-created',) + list_display = ["__str__", "job"] + ordering = ("-created",) diff --git a/jobs/apps.py b/jobs/apps.py index 92868a79c..82271fa74 100644 --- a/jobs/apps.py +++ b/jobs/apps.py @@ -2,9 +2,8 @@ class JobsAppConfig(AppConfig): - - name = 'jobs' - verbose_name = 'Jobs Application' + name = "jobs" + verbose_name = "Jobs Application" def ready(self): import jobs.listeners diff --git a/jobs/factories.py b/jobs/factories.py index a8c38b423..f9cee9b4e 100644 --- a/jobs/factories.py +++ b/jobs/factories.py @@ -13,36 +13,35 @@ class JobProvider(BaseProvider): - job_types = [ - 'Big Data', - 'Cloud', - 'Database', - 'Evangelism', - 'Systems', - 'Test', - 'Web', - 'Operations', + "Big Data", + "Cloud", + "Database", + "Evangelism", + "Systems", + "Test", + "Web", + "Operations", ] job_categories = [ - 'Software Developer', - 'Software Engineer', - 'Data Analyst', - 'Administrator', + "Software Developer", + "Software Engineer", + "Data Analyst", + "Administrator", ] job_titles = [ - 'Senior Python Developer', - 'Django Developer', - 'Full Stack Python/Django Developer', - 'Machine Learning Engineer', - 'Full Stack Developer', - 'Python Data Engineer', - 'Senior Test Automation Engineer', - 'Backend Python Engineer', - 'Python Tech Lead', - 'Junior Developer', + "Senior Python Developer", + "Django Developer", + "Full Stack Python/Django Developer", + "Machine Learning Engineer", + "Full Stack Developer", + "Python Data Engineer", + "Senior Test Automation Engineer", + "Backend Python Engineer", + "Python Tech Lead", + "Junior Developer", ] def job_type(self): @@ -59,42 +58,39 @@ def job_title(self): class JobCategoryFactory(DjangoModelFactory): - class Meta: model = JobCategory - django_get_or_create = ('name',) + django_get_or_create = ("name",) - name = factory.Faker('job_category') + name = factory.Faker("job_category") class JobTypeFactory(DjangoModelFactory): - class Meta: model = JobType - django_get_or_create = ('name',) + django_get_or_create = ("name",) - name = factory.Faker('job_type') + name = factory.Faker("job_type") class JobFactory(DjangoModelFactory): - class Meta: model = Job creator = factory.SubFactory(UserFactory) category = factory.SubFactory(JobCategoryFactory) - job_title = factory.Faker('job_title') - city = 'Lawrence' - region = 'KS' - country = 'US' - company_name = factory.Faker('company') - company_description = factory.Faker('sentence', nb_words=10) - contact = factory.Faker('name') - email = factory.Faker('email') - url = 'https://www.example.com/' - - description = 'Test Description' - requirements = 'Test Requirements' + job_title = factory.Faker("job_title") + city = "Lawrence" + region = "KS" + country = "US" + company_name = factory.Faker("company") + company_description = factory.Faker("sentence", nb_words=10) + contact = factory.Faker("name") + email = factory.Faker("email") + url = "https://www.example.com/" + + description = "Test Description" + requirements = "Test Requirements" @factory.lazy_attribute def expires(self): @@ -143,21 +139,23 @@ class ReviewJobFactory(JobFactory): class JobsBoardAdminGroupFactory(DjangoModelFactory): class Meta: model = Group - django_get_or_create = ('name',) + django_get_or_create = ("name",) - name = 'Job Board Admin' + name = "Job Board Admin" def initial_data(): return { - 'jobs': [ + "jobs": [ ArchivedJobFactory(), DraftJobFactory(), ExpiredJobFactory(), RejectedJobFactory(), RemovedJobFactory(), - ] + ApprovedJobFactory.create_batch(size=5) + ReviewJobFactory.create_batch(size=3), - 'groups': [ + ] + + ApprovedJobFactory.create_batch(size=5) + + ReviewJobFactory.create_batch(size=3), + "groups": [ JobsBoardAdminGroupFactory(), ], } diff --git a/jobs/feeds.py b/jobs/feeds.py index b156d1c61..e7aa80781 100644 --- a/jobs/feeds.py +++ b/jobs/feeds.py @@ -5,10 +5,11 @@ class JobFeed(Feed): - """ Python.org Jobs RSS Feed """ + """Python.org Jobs RSS Feed""" + title = "Python.org Jobs Feed" description = "Python jobs from Python.org" - link = reverse_lazy('jobs:job_list') + link = reverse_lazy("jobs:job_list") def items(self): return Job.objects.approved()[:20] @@ -17,9 +18,11 @@ def item_title(self, item): return item.display_name def item_description(self, item): - """ Description """ - return '\n'.join([ - item.display_location, - item.description.rendered, - item.requirements.rendered, - ]) + """Description""" + return "\n".join( + [ + item.display_location, + item.description.rendered, + item.requirements.rendered, + ] + ) diff --git a/jobs/forms.py b/jobs/forms.py index 08b35ce00..07f5aa78b 100644 --- a/jobs/forms.py +++ b/jobs/forms.py @@ -8,33 +8,33 @@ class JobForm(ContentManageableModelForm): - required_css_class = 'required' + required_css_class = "required" class Meta: model = Job fields = ( - 'job_title', - 'company_name', - 'category', - 'job_types', - 'other_job_type', - 'city', - 'region', - 'country', - 'description', - 'requirements', - 'company_description', - 'contact', - 'email', - 'url', - 'telecommuting', - 'agencies', + "job_title", + "company_name", + "category", + "job_types", + "other_job_type", + "city", + "region", + "country", + "description", + "requirements", + "company_description", + "contact", + "email", + "url", + "telecommuting", + "agencies", ) widgets = { - 'job_types': CheckboxSelectMultiple(), + "job_types": CheckboxSelectMultiple(), } help_texts = { - 'email': ( + "email": ( "This email address will be publicly displayed for " "applicants to contact if they are interested in the " "posting." @@ -43,12 +43,12 @@ class Meta: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['job_types'].help_text = None + self.fields["job_types"].help_text = None def save(self, commit=True): obj = super().save() obj.job_types.clear() - for t in self.cleaned_data['job_types']: + for t in self.cleaned_data["job_types"]: obj.job_types.add(t) return obj @@ -60,12 +60,12 @@ class JobReviewCommentForm(ContentManageableModelForm): class Meta: model = JobReviewComment - fields = ['job', 'comment'] + fields = ["job", "comment"] widgets = { - 'job': HiddenInput(), + "job": HiddenInput(), } def save(self, commit=True): # Don't try to add a new comment if the 'comment' field is empty. - if self.cleaned_data['comment']: + if self.cleaned_data["comment"]: return super().save(commit=commit) diff --git a/jobs/listeners.py b/jobs/listeners.py index 2e88bc793..96290163c 100644 --- a/jobs/listeners.py +++ b/jobs/listeners.py @@ -8,11 +8,14 @@ from .models import Job from .signals import ( - job_was_submitted, job_was_approved, job_was_rejected, comment_was_posted, + job_was_submitted, + job_was_approved, + job_was_rejected, + comment_was_posted, ) # Python job board team email address -EMAIL_JOBS_BOARD = 'jobs@python.org' +EMAIL_JOBS_BOARD = "jobs@python.org" @receiver(comment_was_posted) @@ -24,22 +27,16 @@ def on_comment_was_posted(sender, comment, **kwargs): return False job = comment.job if job.creator is None: - name = job.contact or 'Job Submitter' + name = job.contact or "Job Submitter" else: - name = ( - job.creator.get_full_name() or job.creator.get_username() or - job.contact or 'Job Submitter' - ) + name = job.creator.get_full_name() or job.creator.get_username() or job.contact or "Job Submitter" send_to = [EMAIL_JOBS_BOARD] - reviewer_name = ( - comment.creator.get_full_name() or comment.creator.get_username() or - 'Community Reviewer' - ) + reviewer_name = comment.creator.get_full_name() or comment.creator.get_username() or "Community Reviewer" is_job_board_admin = job.creator.email != comment.creator.email context = { - 'comment': comment.comment.raw, - 'content_object': job, - 'site': Site.objects.get_current(), + "comment": comment.comment.raw, + "content_object": job, + "site": Site.objects.get_current(), } if is_job_board_admin: @@ -47,23 +44,21 @@ def on_comment_was_posted(sender, comment, **kwargs): # job board admins left a comment. send_to.append(job.email) - context['user_name'] = name - context['reviewer_name'] = reviewer_name - template_name = 'comment_was_posted' + context["user_name"] = name + context["reviewer_name"] = reviewer_name + template_name = "comment_was_posted" else: - context['submitter_name'] = name - template_name = 'comment_was_posted_admin' + context["submitter_name"] = name + template_name = "comment_was_posted_admin" - subject = _("Python Job Board: Review comment for: {}").format( - job.display_name) - text_message_template = loader.get_template(f'jobs/email/{template_name}.txt') + subject = _("Python Job Board: Review comment for: {}").format(job.display_name) + text_message_template = loader.get_template(f"jobs/email/{template_name}.txt") text_message = text_message_template.render(context) send_mail(subject, text_message, settings.JOB_FROM_EMAIL, send_to) -def send_job_review_message(job, user, subject_template_path, - message_template_path): +def send_job_review_message(job, user, subject_template_path, message_template_path): """Helper function wrapping logic of sending the review message concerning a job. @@ -71,18 +66,17 @@ def send_job_review_message(job, user, subject_template_path, """ subject_template = loader.get_template(subject_template_path) message_template = loader.get_template(message_template_path) - reviewer_name = user.get_full_name() or user.username or 'Community Reviewer' + reviewer_name = user.get_full_name() or user.username or "Community Reviewer" context = { - 'user_name': job.contact or 'Job Submitter', - 'reviewer_name': reviewer_name, - 'content_object': job, - 'site': Site.objects.get_current(), + "user_name": job.contact or "Job Submitter", + "reviewer_name": reviewer_name, + "content_object": job, + "site": Site.objects.get_current(), } # subject can't contain newlines, thus strip() call subject = subject_template.render(context).strip() message = message_template.render(context) - send_mail(subject, message, settings.JOB_FROM_EMAIL, - [job.email, EMAIL_JOBS_BOARD]) + send_mail(subject, message, settings.JOB_FROM_EMAIL, [job.email, EMAIL_JOBS_BOARD]) @receiver(job_was_approved) @@ -90,9 +84,9 @@ def on_job_was_approved(sender, job, approving_user, **kwargs): """Handle approving job offer. Currently an email should be sent to the person that sent the offer. """ - send_job_review_message(job, approving_user, - 'jobs/email/job_was_approved_subject.txt', - 'jobs/email/job_was_approved.txt') + send_job_review_message( + job, approving_user, "jobs/email/job_was_approved_subject.txt", "jobs/email/job_was_approved.txt" + ) @receiver(job_was_rejected) @@ -100,9 +94,9 @@ def on_job_was_rejected(sender, job, rejecting_user, **kwargs): """Handle rejecting job offer. Currently an email should be sent to the person that sent the offer. """ - send_job_review_message(job, rejecting_user, - 'jobs/email/job_was_rejected_subject.txt', - 'jobs/email/job_was_rejected.txt') + send_job_review_message( + job, rejecting_user, "jobs/email/job_was_rejected_subject.txt", "jobs/email/job_was_rejected.txt" + ) @receiver(job_was_submitted) @@ -111,12 +105,11 @@ def on_job_was_submitted(sender, job, **kwargs): Notify the jobs board when a new job has been submitted for approval """ - subject_template = loader.get_template('jobs/email/job_was_submitted_subject.txt') - message_template = loader.get_template('jobs/email/job_was_submitted.txt') + subject_template = loader.get_template("jobs/email/job_was_submitted_subject.txt") + message_template = loader.get_template("jobs/email/job_was_submitted.txt") - context = {'content_object': job, 'site': Site.objects.get_current()} + context = {"content_object": job, "site": Site.objects.get_current()} subject = subject_template.render(context) message = message_template.render(context) - send_mail(subject, message, settings.JOB_FROM_EMAIL, - [EMAIL_JOBS_BOARD]) + send_mail(subject, message, settings.JOB_FROM_EMAIL, [EMAIL_JOBS_BOARD]) diff --git a/jobs/management/commands/expire_jobs.py b/jobs/management/commands/expire_jobs.py index 8c5848b49..c5050712f 100644 --- a/jobs/management/commands/expire_jobs.py +++ b/jobs/management/commands/expire_jobs.py @@ -8,14 +8,10 @@ class Command(BaseCommand): - """ Expire jobs older than settings.JOB_THRESHOLD_DAYS """ + """Expire jobs older than settings.JOB_THRESHOLD_DAYS""" def handle(self, **options): - days = getattr(settings, 'JOB_THRESHOLD_DAYS', 90) + days = getattr(settings, "JOB_THRESHOLD_DAYS", 90) expiration = timezone.now() - datetime.timedelta(days=days) - Job.objects.approved().filter( - expires__lte=expiration - ).update( - status=Job.STATUS_EXPIRED - ) + Job.objects.approved().filter(expires__lte=expiration).update(status=Job.STATUS_EXPIRED) diff --git a/jobs/management/commands/jobs_monthly_report.py b/jobs/management/commands/jobs_monthly_report.py index 09f37f9b7..90ae7e9e2 100644 --- a/jobs/management/commands/jobs_monthly_report.py +++ b/jobs/management/commands/jobs_monthly_report.py @@ -26,9 +26,7 @@ def handle(self, **options): current_month_jobs = {x["status"]: x["dcount"] for x in current_month_jobs} submissions_current_month = sum(current_month_jobs.values()) - previous_month = ( - datetime.date.today().replace(day=1) - datetime.timedelta(days=1) - ).month + previous_month = (datetime.date.today().replace(day=1) - datetime.timedelta(days=1)).month previous_month_jobs = ( Job.objects.filter(created__month=previous_month) .values("status") @@ -38,9 +36,7 @@ def handle(self, **options): previous_month_jobs = {x["status"]: x["dcount"] for x in previous_month_jobs} submissions_previous_month = sum(previous_month_jobs.values()) - subject_template = loader.get_template( - "jobs/email/monthly_jobs_report_subject.txt" - ) + subject_template = loader.get_template("jobs/email/monthly_jobs_report_subject.txt") message_template = loader.get_template("jobs/email/monthly_jobs_report.txt") context = { diff --git a/jobs/managers.py b/jobs/managers.py index 9799bd1c6..d65278b53 100644 --- a/jobs/managers.py +++ b/jobs/managers.py @@ -5,36 +5,37 @@ class JobTypeQuerySet(QuerySet): - def active(self): - """ active Job Types """ + """active Job Types""" return self.filter(active=True) def with_active_jobs(self): - """ JobTypes with active jobs """ + """JobTypes with active jobs""" now = timezone.now() - return self.active().filter( - jobs__status='approved', - jobs__expires__gte=now, - ).distinct() + return ( + self.active() + .filter( + jobs__status="approved", + jobs__expires__gte=now, + ) + .distinct() + ) class JobCategoryQuerySet(QuerySet): - def active(self): return self.filter(active=True) def with_active_jobs(self): - """ JobCategory with active jobs """ + """JobCategory with active jobs""" now = timezone.now() return self.filter( - jobs__status='approved', + jobs__status="approved", jobs__expires__gte=now, ).distinct() class JobQuerySet(QuerySet): - def approved(self): return self.filter(status=self.model.STATUS_APPROVED) @@ -64,7 +65,7 @@ def review(self): return self.filter( status=self.model.STATUS_REVIEW, created__gte=review_threshold, - ).order_by('created') + ).order_by("created") def moderate(self): return self.exclude(status=self.model.STATUS_REVIEW) diff --git a/jobs/migrations/0001_initial.py b/jobs/migrations/0001_initial.py index c9d2a8ebc..b3eb66a3c 100644 --- a/jobs/migrations/0001_initial.py +++ b/jobs/migrations/0001_initial.py @@ -5,110 +5,188 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('companies', '0001_initial'), + ("companies", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Job', + name="Job", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('company_name', models.CharField(max_length=100, blank=True, null=True)), - ('company_description', markupfield.fields.MarkupField(rendered_field=True, blank=True)), - ('job_title', models.CharField(max_length=100)), - ('company_description_markup_type', models.CharField(max_length=30, choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], default='restructuredtext', blank=True)), - ('_company_description_rendered', models.TextField(editable=False)), - ('city', models.CharField(max_length=100)), - ('region', models.CharField(max_length=100)), - ('country', models.CharField(max_length=100, db_index=True)), - ('location_slug', models.SlugField(max_length=350, editable=False)), - ('country_slug', models.SlugField(max_length=100, editable=False)), - ('description', markupfield.fields.MarkupField(rendered_field=True, blank=True)), - ('description_markup_type', models.CharField(max_length=30, choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], default='restructuredtext', blank=True)), - ('requirements', markupfield.fields.MarkupField(rendered_field=True, blank=True)), - ('contact', models.CharField(max_length=100, blank=True, null=True)), - ('_description_rendered', models.TextField(editable=False)), - ('requirements_markup_type', models.CharField(max_length=30, choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], default='restructuredtext', blank=True)), - ('_requirements_rendered', models.TextField(editable=False)), - ('email', models.EmailField(max_length=75)), - ('url', models.URLField(verbose_name='URL', blank=True, null=True)), - ('status', models.CharField(max_length=20, choices=[('draft', 'draft'), ('review', 'review'), ('approved', 'approved'), ('rejected', 'rejected'), ('archived', 'archived'), ('removed', 'removed'), ('expired', 'expired')], default='review', db_index=True)), - ('dt_start', models.DateTimeField(null=True, verbose_name='Job start date', blank=True)), - ('dt_end', models.DateTimeField(null=True, verbose_name='Job end date', blank=True)), - ('telecommuting', models.BooleanField(default=False)), - ('agencies', models.BooleanField(default=True)), - ('is_featured', models.BooleanField(db_index=True, default=False)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("company_name", models.CharField(max_length=100, blank=True, null=True)), + ("company_description", markupfield.fields.MarkupField(rendered_field=True, blank=True)), + ("job_title", models.CharField(max_length=100)), + ( + "company_description_markup_type", + models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + default="restructuredtext", + blank=True, + ), + ), + ("_company_description_rendered", models.TextField(editable=False)), + ("city", models.CharField(max_length=100)), + ("region", models.CharField(max_length=100)), + ("country", models.CharField(max_length=100, db_index=True)), + ("location_slug", models.SlugField(max_length=350, editable=False)), + ("country_slug", models.SlugField(max_length=100, editable=False)), + ("description", markupfield.fields.MarkupField(rendered_field=True, blank=True)), + ( + "description_markup_type", + models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + default="restructuredtext", + blank=True, + ), + ), + ("requirements", markupfield.fields.MarkupField(rendered_field=True, blank=True)), + ("contact", models.CharField(max_length=100, blank=True, null=True)), + ("_description_rendered", models.TextField(editable=False)), + ( + "requirements_markup_type", + models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + default="restructuredtext", + blank=True, + ), + ), + ("_requirements_rendered", models.TextField(editable=False)), + ("email", models.EmailField(max_length=75)), + ("url", models.URLField(verbose_name="URL", blank=True, null=True)), + ( + "status", + models.CharField( + max_length=20, + choices=[ + ("draft", "draft"), + ("review", "review"), + ("approved", "approved"), + ("rejected", "rejected"), + ("archived", "archived"), + ("removed", "removed"), + ("expired", "expired"), + ], + default="review", + db_index=True, + ), + ), + ("dt_start", models.DateTimeField(null=True, verbose_name="Job start date", blank=True)), + ("dt_end", models.DateTimeField(null=True, verbose_name="Job end date", blank=True)), + ("telecommuting", models.BooleanField(default=False)), + ("agencies", models.BooleanField(default=True)), + ("is_featured", models.BooleanField(db_index=True, default=False)), ], options={ - 'verbose_name': 'job', - 'permissions': [('can_moderate_jobs', 'Can moderate Job listings')], - 'verbose_name_plural': 'jobs', - 'ordering': ('-created',), - 'get_latest_by': 'created', + "verbose_name": "job", + "permissions": [("can_moderate_jobs", "Can moderate Job listings")], + "verbose_name_plural": "jobs", + "ordering": ("-created",), + "get_latest_by": "created", }, bases=(models.Model,), ), migrations.CreateModel( - name='JobCategory', + name="JobCategory", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200)), - ('slug', models.SlugField(unique=True)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=200)), + ("slug", models.SlugField(unique=True)), ], options={ - 'verbose_name': 'job category', - 'verbose_name_plural': 'job categories', - 'ordering': ('name',), + "verbose_name": "job category", + "verbose_name_plural": "job categories", + "ordering": ("name",), }, bases=(models.Model,), ), migrations.CreateModel( - name='JobType', + name="JobType", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200)), - ('slug', models.SlugField(unique=True)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=200)), + ("slug", models.SlugField(unique=True)), ], options={ - 'verbose_name': 'job technologies', - 'verbose_name_plural': 'job technologies', - 'ordering': ('name',), + "verbose_name": "job technologies", + "verbose_name_plural": "job technologies", + "ordering": ("name",), }, bases=(models.Model,), ), migrations.AddField( - model_name='job', - name='category', - field=models.ForeignKey(to='jobs.JobCategory', related_name='jobs', on_delete=models.CASCADE), + model_name="job", + name="category", + field=models.ForeignKey(to="jobs.JobCategory", related_name="jobs", on_delete=models.CASCADE), preserve_default=True, ), migrations.AddField( - model_name='job', - name='company', - field=models.ForeignKey(help_text='Choose a specific company here or enter Name and Description Below', null=True, to='companies.Company', related_name='jobs', blank=True, on_delete=models.CASCADE), + model_name="job", + name="company", + field=models.ForeignKey( + help_text="Choose a specific company here or enter Name and Description Below", + null=True, + to="companies.Company", + related_name="jobs", + blank=True, + on_delete=models.CASCADE, + ), preserve_default=True, ), migrations.AddField( - model_name='job', - name='creator', - field=models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='jobs_job_creator', blank=True, on_delete=models.CASCADE), + model_name="job", + name="creator", + field=models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="jobs_job_creator", + blank=True, + on_delete=models.CASCADE, + ), preserve_default=True, ), migrations.AddField( - model_name='job', - name='job_types', - field=models.ManyToManyField(to='jobs.JobType', verbose_name='Job technologies', related_name='jobs', blank=True), + model_name="job", + name="job_types", + field=models.ManyToManyField( + to="jobs.JobType", verbose_name="Job technologies", related_name="jobs", blank=True + ), preserve_default=True, ), migrations.AddField( - model_name='job', - name='last_modified_by', - field=models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='jobs_job_modified', blank=True, on_delete=models.CASCADE), + model_name="job", + name="last_modified_by", + field=models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="jobs_job_modified", + blank=True, + on_delete=models.CASCADE, + ), preserve_default=True, ), ] diff --git a/jobs/migrations/0002_auto_20150211_1634.py b/jobs/migrations/0002_auto_20150211_1634.py index 8abe5796e..20b5064ba 100644 --- a/jobs/migrations/0002_auto_20150211_1634.py +++ b/jobs/migrations/0002_auto_20150211_1634.py @@ -3,34 +3,53 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0001_initial'), + ("jobs", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='job', - name='description', + model_name="job", + name="description", field=markupfield.fields.MarkupField(rendered_field=True), preserve_default=True, ), migrations.AlterField( - model_name='job', - name='description_markup_type', - field=models.CharField(choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], max_length=30, default='restructuredtext'), + model_name="job", + name="description_markup_type", + field=models.CharField( + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + max_length=30, + default="restructuredtext", + ), preserve_default=True, ), migrations.AlterField( - model_name='job', - name='requirements', + model_name="job", + name="requirements", field=markupfield.fields.MarkupField(rendered_field=True), preserve_default=True, ), migrations.AlterField( - model_name='job', - name='requirements_markup_type', - field=models.CharField(choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], max_length=30, default='restructuredtext'), + model_name="job", + name="requirements_markup_type", + field=models.CharField( + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + max_length=30, + default="restructuredtext", + ), preserve_default=True, ), ] diff --git a/jobs/migrations/0003_auto_20150211_1738.py b/jobs/migrations/0003_auto_20150211_1738.py index 4b65eeec5..d89dfb5f0 100644 --- a/jobs/migrations/0003_auto_20150211_1738.py +++ b/jobs/migrations/0003_auto_20150211_1738.py @@ -5,19 +5,18 @@ def remove_job_submit_sidebar_box(apps, schema_editor): """ Remove jobs-submitajob box """ - Box = apps.get_model('boxes', 'Box') + Box = apps.get_model("boxes", "Box") try: - submit_box = Box.objects.get(label='jobs-submitajob') + submit_box = Box.objects.get(label="jobs-submitajob") submit_box.delete() except Box.DoesNotExist: pass class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0002_auto_20150211_1634'), - ('boxes', '0001_initial'), + ("jobs", "0002_auto_20150211_1634"), + ("boxes", "0001_initial"), ] operations = [ diff --git a/jobs/migrations/0004_auto_20150216_1544.py b/jobs/migrations/0004_auto_20150216_1544.py index 59c15ea4a..05844de92 100644 --- a/jobs/migrations/0004_auto_20150216_1544.py +++ b/jobs/migrations/0004_auto_20150216_1544.py @@ -2,19 +2,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0003_auto_20150211_1738'), + ("jobs", "0003_auto_20150211_1738"), ] operations = [ migrations.RemoveField( - model_name='job', - name='company', + model_name="job", + name="company", ), migrations.AlterField( - model_name='job', - name='company_name', + model_name="job", + name="company_name", field=models.CharField(null=True, max_length=100), preserve_default=True, ), diff --git a/jobs/migrations/0005_job_other_job_type.py b/jobs/migrations/0005_job_other_job_type.py index 01f6caecd..5c07212e2 100644 --- a/jobs/migrations/0005_job_other_job_type.py +++ b/jobs/migrations/0005_job_other_job_type.py @@ -2,16 +2,15 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0004_auto_20150216_1544'), + ("jobs", "0004_auto_20150216_1544"), ] operations = [ migrations.AddField( - model_name='job', - name='other_job_type', - field=models.CharField(max_length=100, verbose_name='Other Job Technologies', blank=True), + model_name="job", + name="other_job_type", + field=models.CharField(max_length=100, verbose_name="Other Job Technologies", blank=True), preserve_default=True, ), ] diff --git a/jobs/migrations/0006_region_nullable.py b/jobs/migrations/0006_region_nullable.py index 4dc28b990..833884ffc 100644 --- a/jobs/migrations/0006_region_nullable.py +++ b/jobs/migrations/0006_region_nullable.py @@ -2,15 +2,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0005_job_other_job_type'), + ("jobs", "0005_job_other_job_type"), ] operations = [ migrations.AlterField( - model_name='job', - name='region', + model_name="job", + name="region", field=models.CharField(blank=True, max_length=100, null=True), preserve_default=True, ), diff --git a/jobs/migrations/0007_auto_20150227_2223.py b/jobs/migrations/0007_auto_20150227_2223.py index 8b1fbf1eb..ff8a32826 100644 --- a/jobs/migrations/0007_auto_20150227_2223.py +++ b/jobs/migrations/0007_auto_20150227_2223.py @@ -2,24 +2,23 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0006_region_nullable'), + ("jobs", "0006_region_nullable"), ] operations = [ migrations.RemoveField( - model_name='job', - name='dt_end', + model_name="job", + name="dt_end", ), migrations.RemoveField( - model_name='job', - name='dt_start', + model_name="job", + name="dt_start", ), migrations.AddField( - model_name='job', - name='expires', - field=models.DateTimeField(blank=True, null=True, verbose_name='Job Listing Expiration Date'), + model_name="job", + name="expires", + field=models.DateTimeField(blank=True, null=True, verbose_name="Job Listing Expiration Date"), preserve_default=True, ), ] diff --git a/jobs/migrations/0008_auto_20150316_1205.py b/jobs/migrations/0008_auto_20150316_1205.py index b969e794b..f62458197 100644 --- a/jobs/migrations/0008_auto_20150316_1205.py +++ b/jobs/migrations/0008_auto_20150316_1205.py @@ -2,28 +2,27 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0007_auto_20150227_2223'), + ("jobs", "0007_auto_20150227_2223"), ] operations = [ migrations.AddField( - model_name='jobcategory', - name='active', + model_name="jobcategory", + name="active", field=models.BooleanField(default=True), preserve_default=True, ), migrations.AddField( - model_name='jobtype', - name='active', + model_name="jobtype", + name="active", field=models.BooleanField(default=True), preserve_default=True, ), migrations.AlterField( - model_name='job', - name='region', - field=models.CharField(blank=True, verbose_name='State, Province or Region', max_length=100, default=''), + model_name="job", + name="region", + field=models.CharField(blank=True, verbose_name="State, Province or Region", max_length=100, default=""), preserve_default=False, ), ] diff --git a/jobs/migrations/0009_auto_20150317_1815.py b/jobs/migrations/0009_auto_20150317_1815.py index b7e51803a..4e1bb4dcd 100644 --- a/jobs/migrations/0009_auto_20150317_1815.py +++ b/jobs/migrations/0009_auto_20150317_1815.py @@ -3,52 +3,51 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0008_auto_20150316_1205'), + ("jobs", "0008_auto_20150316_1205"), ] operations = [ migrations.AlterField( - model_name='job', - name='agencies', - field=models.BooleanField(verbose_name='Agencies are OK to contact?', default=True), + model_name="job", + name="agencies", + field=models.BooleanField(verbose_name="Agencies are OK to contact?", default=True), preserve_default=True, ), migrations.AlterField( - model_name='job', - name='contact', - field=models.CharField(verbose_name='Contact name', blank=True, null=True, max_length=100), + model_name="job", + name="contact", + field=models.CharField(verbose_name="Contact name", blank=True, null=True, max_length=100), preserve_default=True, ), migrations.AlterField( - model_name='job', - name='description', - field=markupfield.fields.MarkupField(verbose_name='Job description', rendered_field=True), + model_name="job", + name="description", + field=markupfield.fields.MarkupField(verbose_name="Job description", rendered_field=True), preserve_default=True, ), migrations.AlterField( - model_name='job', - name='email', - field=models.EmailField(verbose_name='Contact email', max_length=75), + model_name="job", + name="email", + field=models.EmailField(verbose_name="Contact email", max_length=75), preserve_default=True, ), migrations.AlterField( - model_name='job', - name='other_job_type', - field=models.CharField(verbose_name='Other job technologies', blank=True, max_length=100), + model_name="job", + name="other_job_type", + field=models.CharField(verbose_name="Other job technologies", blank=True, max_length=100), preserve_default=True, ), migrations.AlterField( - model_name='job', - name='requirements', - field=markupfield.fields.MarkupField(verbose_name='Job requirements', rendered_field=True), + model_name="job", + name="requirements", + field=markupfield.fields.MarkupField(verbose_name="Job requirements", rendered_field=True), preserve_default=True, ), migrations.AlterField( - model_name='job', - name='telecommuting', - field=models.BooleanField(verbose_name='Telecommuting allowed?', default=False), + model_name="job", + name="telecommuting", + field=models.BooleanField(verbose_name="Telecommuting allowed?", default=False), preserve_default=True, ), ] diff --git a/jobs/migrations/0010_auto_20150416_1853.py b/jobs/migrations/0010_auto_20150416_1853.py index 548ffe4b6..e41985069 100644 --- a/jobs/migrations/0010_auto_20150416_1853.py +++ b/jobs/migrations/0010_auto_20150416_1853.py @@ -2,28 +2,58 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0009_auto_20150317_1815'), + ("jobs", "0009_auto_20150317_1815"), ] operations = [ migrations.AlterField( - model_name='job', - name='company_description_markup_type', - field=models.CharField(max_length=30, choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')], default='restructuredtext', blank=True), + model_name="job", + name="company_description_markup_type", + field=models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + default="restructuredtext", + blank=True, + ), preserve_default=True, ), migrations.AlterField( - model_name='job', - name='description_markup_type', - field=models.CharField(max_length=30, default='restructuredtext', choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')]), + model_name="job", + name="description_markup_type", + field=models.CharField( + max_length=30, + default="restructuredtext", + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + ), preserve_default=True, ), migrations.AlterField( - model_name='job', - name='requirements_markup_type', - field=models.CharField(max_length=30, default='restructuredtext', choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')]), + model_name="job", + name="requirements_markup_type", + field=models.CharField( + max_length=30, + default="restructuredtext", + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + ), preserve_default=True, ), ] diff --git a/jobs/migrations/0011_jobreviewcomment.py b/jobs/migrations/0011_jobreviewcomment.py index f1eb377bb..e0e05de92 100644 --- a/jobs/migrations/0011_jobreviewcomment.py +++ b/jobs/migrations/0011_jobreviewcomment.py @@ -5,28 +5,58 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('jobs', '0010_auto_20150416_1853'), + ("jobs", "0010_auto_20150416_1853"), ] operations = [ migrations.CreateModel( - name='JobReviewComment', + name="JobReviewComment", fields=[ - ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), - ('created', models.DateTimeField(blank=True, default=django.utils.timezone.now, db_index=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('comment', markupfield.fields.MarkupField(rendered_field=True)), - ('comment_markup_type', models.CharField(choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')], max_length=30, default='restructuredtext')), - ('_comment_rendered', models.TextField(editable=False)), - ('creator', models.ForeignKey(related_name='jobs_jobreviewcomment_creator', to=settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.CASCADE)), - ('job', models.ForeignKey(related_name='review_comments', to='jobs.Job', on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(related_name='jobs_jobreviewcomment_modified', to=settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(serialize=False, verbose_name="ID", primary_key=True, auto_created=True)), + ("created", models.DateTimeField(blank=True, default=django.utils.timezone.now, db_index=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("comment", markupfield.fields.MarkupField(rendered_field=True)), + ( + "comment_markup_type", + models.CharField( + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + max_length=30, + default="restructuredtext", + ), + ), + ("_comment_rendered", models.TextField(editable=False)), + ( + "creator", + models.ForeignKey( + related_name="jobs_jobreviewcomment_creator", + to=settings.AUTH_USER_MODEL, + null=True, + blank=True, + on_delete=models.CASCADE, + ), + ), + ("job", models.ForeignKey(related_name="review_comments", to="jobs.Job", on_delete=models.CASCADE)), + ( + "last_modified_by", + models.ForeignKey( + related_name="jobs_jobreviewcomment_modified", + to=settings.AUTH_USER_MODEL, + null=True, + blank=True, + on_delete=models.CASCADE, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, bases=(models.Model,), ), diff --git a/jobs/migrations/0012_auto_20170809_1849.py b/jobs/migrations/0012_auto_20170809_1849.py index ba9d78d4f..1330db4cb 100644 --- a/jobs/migrations/0012_auto_20170809_1849.py +++ b/jobs/migrations/0012_auto_20170809_1849.py @@ -3,22 +3,22 @@ from django.db import models, migrations from django.utils.timezone import now -MARKER = '.. Migrated from django_comments_xtd.Comment model.\n\n' +MARKER = ".. Migrated from django_comments_xtd.Comment model.\n\n" -comments_app_name = 'django_comments_xtd' -content_type = 'job' +comments_app_name = "django_comments_xtd" +content_type = "job" def migrate_old_content(apps, schema_editor): try: - Comment = apps.get_model(comments_app_name, 'XtdComment') + Comment = apps.get_model(comments_app_name, "XtdComment") except LookupError: # django_comments_xtd isn't installed. return - create_contenttypes(apps.app_configs['contenttypes']) - JobReviewComment = apps.get_model('jobs', 'JobReviewComment') - Job = apps.get_model('jobs', 'Job') - ContentType = apps.get_model('contenttypes', 'ContentType') + create_contenttypes(apps.app_configs["contenttypes"]) + JobReviewComment = apps.get_model("jobs", "JobReviewComment") + Job = apps.get_model("jobs", "Job") + ContentType = apps.get_model("contenttypes", "ContentType") db_alias = schema_editor.connection.alias try: # 'ContentType.name' is now a property in Django 1.8 so we @@ -27,7 +27,9 @@ def migrate_old_content(apps, schema_editor): except ContentType.DoesNotExist: return old_comments = Comment.objects.using(db_alias).filter( - content_type=job_contenttype.pk, is_public=True, is_removed=False, + content_type=job_contenttype.pk, + is_public=True, + is_removed=False, ) found_jobs = {} comments = [] @@ -52,19 +54,18 @@ def migrate_old_content(apps, schema_editor): def delete_migrated_content(apps, schema_editor): - JobReviewComment = apps.get_model('jobs', 'JobReviewComment') + JobReviewComment = apps.get_model("jobs", "JobReviewComment") db_alias = schema_editor.connection.alias JobReviewComment.objects.using(db_alias).filter(comment__startswith=MARKER).delete() class Migration(migrations.Migration): - dependencies = [ - ('contenttypes', '0001_initial'), - ('jobs', '0011_jobreviewcomment'), + ("contenttypes", "0001_initial"), + ("jobs", "0011_jobreviewcomment"), ] if global_apps.is_installed(comments_app_name): - dependencies.append((comments_app_name, '0001_initial')) + dependencies.append((comments_app_name, "0001_initial")) operations = [ migrations.RunPython(migrate_old_content, delete_migrated_content), diff --git a/jobs/migrations/0013_auto_20170810_1625.py b/jobs/migrations/0013_auto_20170810_1625.py index b7c4a173e..58931fdb8 100644 --- a/jobs/migrations/0013_auto_20170810_1625.py +++ b/jobs/migrations/0013_auto_20170810_1625.py @@ -2,14 +2,13 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0012_auto_20170809_1849'), + ("jobs", "0012_auto_20170809_1849"), ] operations = [ migrations.AlterModelOptions( - name='jobreviewcomment', - options={'ordering': ('created',)}, + name="jobreviewcomment", + options={"ordering": ("created",)}, ), ] diff --git a/jobs/migrations/0013_auto_20170810_1627.py b/jobs/migrations/0013_auto_20170810_1627.py index b7c4a173e..58931fdb8 100644 --- a/jobs/migrations/0013_auto_20170810_1627.py +++ b/jobs/migrations/0013_auto_20170810_1627.py @@ -2,14 +2,13 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0012_auto_20170809_1849'), + ("jobs", "0012_auto_20170809_1849"), ] operations = [ migrations.AlterModelOptions( - name='jobreviewcomment', - options={'ordering': ('created',)}, + name="jobreviewcomment", + options={"ordering": ("created",)}, ), ] diff --git a/jobs/migrations/0014_merge.py b/jobs/migrations/0014_merge.py index 0b65016da..9ff2b8428 100644 --- a/jobs/migrations/0014_merge.py +++ b/jobs/migrations/0014_merge.py @@ -2,11 +2,9 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0013_auto_20170810_1627'), - ('jobs', '0013_auto_20170810_1625'), + ("jobs", "0013_auto_20170810_1627"), + ("jobs", "0013_auto_20170810_1625"), ] - operations = [ - ] + operations = [] diff --git a/jobs/migrations/0015_auto_20170814_0301.py b/jobs/migrations/0015_auto_20170814_0301.py index bb4d8427e..04da55556 100644 --- a/jobs/migrations/0015_auto_20170814_0301.py +++ b/jobs/migrations/0015_auto_20170814_0301.py @@ -2,15 +2,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0014_merge'), + ("jobs", "0014_merge"), ] operations = [ migrations.AlterField( - model_name='job', - name='email', - field=models.EmailField(max_length=254, verbose_name='Contact email'), + model_name="job", + name="email", + field=models.EmailField(max_length=254, verbose_name="Contact email"), ), ] diff --git a/jobs/migrations/0016_auto_20170821_2000.py b/jobs/migrations/0016_auto_20170821_2000.py index 077d24858..55e456da9 100644 --- a/jobs/migrations/0016_auto_20170821_2000.py +++ b/jobs/migrations/0016_auto_20170821_2000.py @@ -2,15 +2,24 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0015_auto_20170814_0301'), + ("jobs", "0015_auto_20170814_0301"), ] operations = [ migrations.AlterField( - model_name='job', - name='company_description_markup_type', - field=models.CharField(choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')], default='restructuredtext', max_length=30), + model_name="job", + name="company_description_markup_type", + field=models.CharField( + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + default="restructuredtext", + max_length=30, + ), ), ] diff --git a/jobs/migrations/0017_auto_20180705_0348.py b/jobs/migrations/0017_auto_20180705_0348.py index 983142351..70012e359 100644 --- a/jobs/migrations/0017_auto_20180705_0348.py +++ b/jobs/migrations/0017_auto_20180705_0348.py @@ -5,20 +5,30 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0016_auto_20170821_2000'), + ("jobs", "0016_auto_20170821_2000"), ] operations = [ migrations.AlterField( - model_name='job', - name='category', - field=models.ForeignKey(limit_choices_to={'active': True}, on_delete=django.db.models.deletion.CASCADE, related_name='jobs', to='jobs.JobCategory'), + model_name="job", + name="category", + field=models.ForeignKey( + limit_choices_to={"active": True}, + on_delete=django.db.models.deletion.CASCADE, + related_name="jobs", + to="jobs.JobCategory", + ), ), migrations.AlterField( - model_name='job', - name='job_types', - field=models.ManyToManyField(blank=True, limit_choices_to={'active': True}, related_name='jobs', to='jobs.JobType', verbose_name='Job technologies'), + model_name="job", + name="job_types", + field=models.ManyToManyField( + blank=True, + limit_choices_to={"active": True}, + related_name="jobs", + to="jobs.JobType", + verbose_name="Job technologies", + ), ), ] diff --git a/jobs/migrations/0018_auto_20180705_0352.py b/jobs/migrations/0018_auto_20180705_0352.py index 1c25f0080..e1293dac8 100644 --- a/jobs/migrations/0018_auto_20180705_0352.py +++ b/jobs/migrations/0018_auto_20180705_0352.py @@ -4,20 +4,19 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0017_auto_20180705_0348'), + ("jobs", "0017_auto_20180705_0348"), ] operations = [ migrations.AlterField( - model_name='jobcategory', - name='slug', + model_name="jobcategory", + name="slug", field=models.SlugField(max_length=200, unique=True), ), migrations.AlterField( - model_name='jobtype', - name='slug', + model_name="jobtype", + name="slug", field=models.SlugField(max_length=200, unique=True), ), ] diff --git a/jobs/migrations/0019_job_submitted_by.py b/jobs/migrations/0019_job_submitted_by.py index 62c74fa3a..5e48a1bef 100644 --- a/jobs/migrations/0019_job_submitted_by.py +++ b/jobs/migrations/0019_job_submitted_by.py @@ -6,16 +6,17 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('jobs', '0018_auto_20180705_0352'), + ("jobs", "0018_auto_20180705_0352"), ] operations = [ migrations.AddField( - model_name='job', - name='submitted_by', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + model_name="job", + name="submitted_by", + field=models.ForeignKey( + null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL + ), ), ] diff --git a/jobs/migrations/0020_auto_20191101_1601.py b/jobs/migrations/0020_auto_20191101_1601.py index d6c9c26e9..60309750e 100644 --- a/jobs/migrations/0020_auto_20191101_1601.py +++ b/jobs/migrations/0020_auto_20191101_1601.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0019_job_submitted_by'), + ("jobs", "0019_job_submitted_by"), ] operations = [ migrations.AlterField( - model_name='job', - name='url', - field=models.URLField(null=True, verbose_name='URL'), + model_name="job", + name="url", + field=models.URLField(null=True, verbose_name="URL"), ), ] diff --git a/jobs/migrations/0021_alter_job_creator_alter_job_last_modified_by_and_more.py b/jobs/migrations/0021_alter_job_creator_alter_job_last_modified_by_and_more.py index a82f65ac9..b14b38a1b 100644 --- a/jobs/migrations/0021_alter_job_creator_alter_job_last_modified_by_and_more.py +++ b/jobs/migrations/0021_alter_job_creator_alter_job_last_modified_by_and_more.py @@ -6,31 +6,54 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('jobs', '0020_auto_20191101_1601'), + ("jobs", "0020_auto_20191101_1601"), ] operations = [ migrations.AlterField( - model_name='job', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="job", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='job', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="job", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='jobreviewcomment', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="jobreviewcomment", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='jobreviewcomment', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="jobreviewcomment", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/jobs/models.py b/jobs/models.py index 54722873d..be1524d0b 100644 --- a/jobs/models.py +++ b/jobs/models.py @@ -16,12 +16,10 @@ from users.models import User from .managers import JobQuerySet, JobTypeQuerySet, JobCategoryQuerySet -from .signals import ( - job_was_submitted, job_was_approved, job_was_rejected, comment_was_posted -) +from .signals import job_was_submitted, job_was_approved, job_was_rejected, comment_was_posted -DEFAULT_MARKUP_TYPE = getattr(settings, 'DEFAULT_MARKUP_TYPE', 'restructuredtext') +DEFAULT_MARKUP_TYPE = getattr(settings, "DEFAULT_MARKUP_TYPE", "restructuredtext") class JobType(NameSlugModel): @@ -30,9 +28,9 @@ class JobType(NameSlugModel): objects = JobTypeQuerySet.as_manager() class Meta: - verbose_name = 'job technologies' - verbose_name_plural = 'job technologies' - ordering = ('name', ) + verbose_name = "job technologies" + verbose_name_plural = "job technologies" + ordering = ("name",) class JobCategory(NameSlugModel): @@ -41,9 +39,9 @@ class JobCategory(NameSlugModel): objects = JobCategoryQuerySet.as_manager() class Meta: - verbose_name = 'job category' - verbose_name_plural = 'job categories' - ordering = ('name', ) + verbose_name = "job category" + verbose_name_plural = "job categories" + ordering = ("name",) class Job(ContentManageable): @@ -51,65 +49,38 @@ class Job(ContentManageable): category = models.ForeignKey( JobCategory, - related_name='jobs', - limit_choices_to={'active': True}, + related_name="jobs", + limit_choices_to={"active": True}, on_delete=models.CASCADE, ) job_types = models.ManyToManyField( JobType, - related_name='jobs', + related_name="jobs", blank=True, - verbose_name='Job technologies', - limit_choices_to={'active': True}, + verbose_name="Job technologies", + limit_choices_to={"active": True}, ) other_job_type = models.CharField( - verbose_name='Other job technologies', + verbose_name="Other job technologies", max_length=100, blank=True, ) - company_name = models.CharField( - max_length=100, - null=True) - company_description = MarkupField( - blank=True, - default_markup_type=DEFAULT_MARKUP_TYPE) - job_title = models.CharField( - max_length=100) - - city = models.CharField( - max_length=100) - region = models.CharField( - verbose_name='State, Province or Region', - blank=True, - max_length=100) - country = models.CharField( - max_length=100, - db_index=True) - location_slug = models.SlugField( - max_length=350, - editable=False) - country_slug = models.SlugField( - max_length=100, - editable=False) + company_name = models.CharField(max_length=100, null=True) + company_description = MarkupField(blank=True, default_markup_type=DEFAULT_MARKUP_TYPE) + job_title = models.CharField(max_length=100) - description = MarkupField( - verbose_name='Job description', - default_markup_type=DEFAULT_MARKUP_TYPE) - requirements = MarkupField( - verbose_name='Job requirements', - default_markup_type=DEFAULT_MARKUP_TYPE) + city = models.CharField(max_length=100) + region = models.CharField(verbose_name="State, Province or Region", blank=True, max_length=100) + country = models.CharField(max_length=100, db_index=True) + location_slug = models.SlugField(max_length=350, editable=False) + country_slug = models.SlugField(max_length=100, editable=False) - contact = models.CharField( - verbose_name='Contact name', - null=True, - blank=True, - max_length=100) - email = models.EmailField( - verbose_name='Contact email') - url = models.URLField( - verbose_name='URL', - null=True, - blank=False) + description = MarkupField(verbose_name="Job description", default_markup_type=DEFAULT_MARKUP_TYPE) + requirements = MarkupField(verbose_name="Job requirements", default_markup_type=DEFAULT_MARKUP_TYPE) + + contact = models.CharField(verbose_name="Contact name", null=True, blank=True, max_length=100) + email = models.EmailField(verbose_name="Contact email") + url = models.URLField(verbose_name="URL", null=True, blank=False) submitted_by = models.ForeignKey( User, @@ -117,60 +88,49 @@ class Job(ContentManageable): on_delete=models.SET_NULL, ) - STATUS_DRAFT = 'draft' - STATUS_REVIEW = 'review' - STATUS_APPROVED = 'approved' - STATUS_REJECTED = 'rejected' - STATUS_ARCHIVED = 'archived' - STATUS_REMOVED = 'removed' - STATUS_EXPIRED = 'expired' + STATUS_DRAFT = "draft" + STATUS_REVIEW = "review" + STATUS_APPROVED = "approved" + STATUS_REJECTED = "rejected" + STATUS_ARCHIVED = "archived" + STATUS_REMOVED = "removed" + STATUS_EXPIRED = "expired" STATUS_CHOICES = ( - (STATUS_DRAFT, 'draft'), - (STATUS_REVIEW, 'review'), - (STATUS_APPROVED, 'approved'), - (STATUS_REJECTED, 'rejected'), - (STATUS_ARCHIVED, 'archived'), - (STATUS_REMOVED, 'removed'), - (STATUS_EXPIRED, 'expired'), + (STATUS_DRAFT, "draft"), + (STATUS_REVIEW, "review"), + (STATUS_APPROVED, "approved"), + (STATUS_REJECTED, "rejected"), + (STATUS_ARCHIVED, "archived"), + (STATUS_REMOVED, "removed"), + (STATUS_EXPIRED, "expired"), ) - status = models.CharField( - max_length=20, - choices=STATUS_CHOICES, - default=STATUS_REVIEW, - db_index=True) - expires = models.DateTimeField( - verbose_name='Job Listing Expiration Date', - blank=True, - null=True) + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_REVIEW, db_index=True) + expires = models.DateTimeField(verbose_name="Job Listing Expiration Date", blank=True, null=True) - telecommuting = models.BooleanField( - verbose_name='Telecommuting allowed?', - default=False) - agencies = models.BooleanField( - verbose_name='Agencies are OK to contact?', - default=True) + telecommuting = models.BooleanField(verbose_name="Telecommuting allowed?", default=False) + agencies = models.BooleanField(verbose_name="Agencies are OK to contact?", default=True) is_featured = models.BooleanField(default=False, db_index=True) objects = JobQuerySet.as_manager() class Meta: - ordering = ('-created',) - get_latest_by = 'created' - verbose_name = 'job' - verbose_name_plural = 'jobs' - permissions = [('can_moderate_jobs', 'Can moderate Job listings')] + ordering = ("-created",) + get_latest_by = "created" + verbose_name = "job" + verbose_name_plural = "jobs" + permissions = [("can_moderate_jobs", "Can moderate Job listings")] def __str__(self): - return f'Job Listing #{self.pk}' + return f"Job Listing #{self.pk}" def save(self, **kwargs): location_parts = (self.city, self.region, self.country) - location_str = '' + location_str = "" for location_part in location_parts: if location_part is not None: - location_str = ' '.join([location_str, location_part]) + location_str = " ".join([location_str, location_part]) self.location_slug = slugify(location_str) self.country_slug = slugify(self.country) @@ -196,8 +156,7 @@ def approve(self, approving_user): """ self.status = Job.STATUS_APPROVED self.save() - job_was_approved.send(sender=self.__class__, job=self, - approving_user=approving_user) + job_was_approved.send(sender=self.__class__, job=self, approving_user=approving_user) def reject(self, rejecting_user): """Updates job status to Job.STATUS_REJECTED after rejection was issued @@ -205,11 +164,10 @@ def reject(self, rejecting_user): """ self.status = Job.STATUS_REJECTED self.save() - job_was_rejected.send(sender=self.__class__, job=self, - rejecting_user=rejecting_user) + job_was_rejected.send(sender=self.__class__, job=self, rejecting_user=rejecting_user) def get_absolute_url(self): - return reverse('jobs:job_detail', kwargs={'pk': self.pk}) + return reverse("jobs:job_detail", kwargs={"pk": self.pk}) @property def display_name(self): @@ -221,9 +179,8 @@ def display_description(self): @property def display_location(self): - location_parts = [part for part in (self.city, self.region, self.country) - if part] - location_str = ', '.join(location_parts) + location_parts = [part for part in (self.city, self.region, self.country) if part] + location_str = ", ".join(location_parts) return location_str @property @@ -232,11 +189,7 @@ def is_new(self): @property def editable(self): - return self.status in ( - self.STATUS_DRAFT, - self.STATUS_REVIEW, - self.STATUS_REJECTED - ) + return self.status in (self.STATUS_DRAFT, self.STATUS_REVIEW, self.STATUS_REJECTED) def get_previous_listing(self): return self.get_previous_by_created(status=self.STATUS_APPROVED) @@ -246,18 +199,18 @@ def get_next_listing(self): class JobReviewComment(ContentManageable): - job = models.ForeignKey(Job, related_name='review_comments', on_delete=models.CASCADE) + job = models.ForeignKey(Job, related_name="review_comments", on_delete=models.CASCADE) comment = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE) class Meta: - ordering = ('created',) + ordering = ("created",) def save(self, **kwargs): comment_was_posted.send(sender=self.__class__, comment=self) return super().save(**kwargs) def __str__(self): - return f'' + return f"" @receiver(post_save, sender=Job) @@ -267,10 +220,10 @@ def purge_fastly_cache(sender, instance, **kwargs): Requires settings.FASTLY_API_KEY being set """ # Skip in fixtures - if kwargs.get('raw', False): + if kwargs.get("raw", False): return if instance.status == Job.STATUS_APPROVED: - purge_url(reverse('jobs:job_detail', kwargs={'pk': instance.pk})) - purge_url(reverse('jobs:job_list')) - purge_url(reverse('jobs:job_rss')) + purge_url(reverse("jobs:job_detail", kwargs={"pk": instance.pk})) + purge_url(reverse("jobs:job_list")) + purge_url(reverse("jobs:job_rss")) diff --git a/jobs/search_indexes.py b/jobs/search_indexes.py index 91f56fa58..05d6ca35f 100644 --- a/jobs/search_indexes.py +++ b/jobs/search_indexes.py @@ -8,7 +8,7 @@ class JobTypeIndex(indexes.SearchIndex, indexes.Indexable): text = indexes.CharField(document=True, use_template=True) - name = indexes.CharField(model_attr='name') + name = indexes.CharField(model_attr="name") path = indexes.CharField() include_template = indexes.CharField() @@ -23,17 +23,17 @@ def prepare_include_template(self, obj): return "search/includes/jobs.job_type.html" def prepare_path(self, obj): - return reverse('jobs:job_list_type', kwargs={'slug': obj.slug}) + return reverse("jobs:job_list_type", kwargs={"slug": obj.slug}) def prepare(self, obj): data = super().prepare(obj) - data['boost'] = 1.3 + data["boost"] = 1.3 return data class JobCategoryIndex(indexes.SearchIndex, indexes.Indexable): text = indexes.CharField(document=True, use_template=True) - name = indexes.CharField(model_attr='name') + name = indexes.CharField(model_attr="name") path = indexes.CharField() include_template = indexes.CharField() @@ -48,21 +48,21 @@ def prepare_include_template(self, obj): return "search/includes/jobs.job_category.html" def prepare_path(self, obj): - return reverse('jobs:job_list_category', kwargs={'slug': obj.slug}) + return reverse("jobs:job_list_category", kwargs={"slug": obj.slug}) def prepare(self, obj): data = super().prepare(obj) - data['boost'] = 1.4 + data["boost"] = 1.4 return data class JobIndex(indexes.SearchIndex, indexes.Indexable): text = indexes.CharField(document=True, use_template=True) - name = indexes.CharField(model_attr='job_title') - city = indexes.CharField(model_attr='city') - region = indexes.CharField(model_attr='region') - country = indexes.CharField(model_attr='country') - telecommuting = indexes.BooleanField(model_attr='telecommuting') + name = indexes.CharField(model_attr="job_title") + city = indexes.CharField(model_attr="city") + region = indexes.CharField(model_attr="region") + country = indexes.CharField(model_attr="country") + telecommuting = indexes.BooleanField(model_attr="telecommuting") description = indexes.CharField() @@ -83,9 +83,9 @@ def prepare_description(self, obj): return striptags(truncatewords_html(obj.description.rendered, 50)) def prepare_path(self, obj): - return reverse('jobs:job_detail', kwargs={'pk': obj.pk}) + return reverse("jobs:job_detail", kwargs={"pk": obj.pk}) def prepare(self, obj): data = super().prepare(obj) - data['boost'] = 1.1 + data["boost"] = 1.1 return data diff --git a/jobs/tests/test_models.py b/jobs/tests/test_models.py index 310659165..bd33c20ad 100644 --- a/jobs/tests/test_models.py +++ b/jobs/tests/test_models.py @@ -9,11 +9,10 @@ class JobsModelsTests(TestCase): - def create_job(self, **kwargs): job_kwargs = { - 'city': "Memphis", - 'region': "TN", + "city": "Memphis", + "region": "TN", "country": "USA", } job_kwargs.update(**kwargs) @@ -32,7 +31,7 @@ def test_is_new(self): def test_location_slug(self): job = self.create_job() - self.assertEqual(job.location_slug, 'memphis-tn-usa') + self.assertEqual(job.location_slug, "memphis-tn-usa") def test_approved_manager(self): self.assertEqual(Job.objects.approved().count(), 0) @@ -83,7 +82,7 @@ def test_visible_manager(self): def test_job_type_with_active_jobs_manager(self): t1 = factories.JobTypeFactory() - t2 = factories.JobTypeFactory(name='Spam') + t2 = factories.JobTypeFactory(name="Spam") j1 = factories.ApprovedJobFactory() j1.job_types.add(t1) @@ -94,7 +93,7 @@ def test_job_type_with_active_jobs_manager(self): def test_job_category_with_active_jobs_manager(self): c1 = factories.JobCategoryFactory() - c2 = factories.JobCategoryFactory(name='Foo') + c2 = factories.JobCategoryFactory(name="Foo") j1 = factories.ApprovedJobFactory() j1.category = c1 j1.save() @@ -122,14 +121,14 @@ def test_get_previous_approved(self): self.assertEqual(job2.get_previous_listing(), job1) def test_region_optional(self): - job = self.create_job(region='') + job = self.create_job(region="") self.assertEqual(job.city, "Memphis") self.assertEqual(job.country, "USA") self.assertFalse(job.region) def test_display_location(self): job1 = self.create_job() - self.assertEqual(job1.display_location, 'Memphis, TN, USA') + self.assertEqual(job1.display_location, "Memphis, TN, USA") - job2 = self.create_job(region='') - self.assertEqual(job2.display_location, 'Memphis, USA') + job2 = self.create_job(region="") + self.assertEqual(job2.display_location, "Memphis, USA") diff --git a/jobs/tests/test_views.py b/jobs/tests/test_views.py index 763dca666..026a1436c 100644 --- a/jobs/tests/test_views.py +++ b/jobs/tests/test_views.py @@ -5,41 +5,39 @@ from ..models import Job from ..factories import ( - ApprovedJobFactory, DraftJobFactory, JobCategoryFactory, JobTypeFactory, - ReviewJobFactory, JobsBoardAdminGroupFactory, + ApprovedJobFactory, + DraftJobFactory, + JobCategoryFactory, + JobTypeFactory, + ReviewJobFactory, + JobsBoardAdminGroupFactory, ) from users.factories import UserFactory class JobsViewTests(TestCase): def setUp(self): - self.user = UserFactory(password='password') + self.user = UserFactory(password="password") - self.user2 = UserFactory(password='password') + self.user2 = UserFactory(password="password") self.staff = UserFactory( - password='password', + password="password", is_staff=True, groups=[JobsBoardAdminGroupFactory()], ) - self.job_category = JobCategoryFactory( - name='Game Production', - slug='game-production' - ) + self.job_category = JobCategoryFactory(name="Game Production", slug="game-production") - self.job_type = JobTypeFactory( - name='FrontEnd Developer', - slug='frontend-developer' - ) + self.job_type = JobTypeFactory(name="FrontEnd Developer", slug="frontend-developer") self.job = ApprovedJobFactory( - description='Lorem ipsum dolor sit amet', + description="Lorem ipsum dolor sit amet", category=self.job_category, - city='Memphis', - region='TN', - country='USA', - email='hr@company.com', + city="Memphis", + region="TN", + country="USA", + email="hr@company.com", is_featured=True, telecommuting=True, creator=self.user, @@ -47,189 +45,186 @@ def setUp(self): self.job.job_types.add(self.job_type) self.job_draft = DraftJobFactory( - description='Lorem ipsum dolor sit amet', + description="Lorem ipsum dolor sit amet", category=self.job_category, - city='Memphis', - region='TN', - country='USA', - email='hr@company.com', + city="Memphis", + region="TN", + country="USA", + email="hr@company.com", is_featured=True, creator=self.user, ) self.job_draft.job_types.add(self.job_type) def test_job_list(self): - url = reverse('jobs:job_list') + url = reverse("jobs:job_list") response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'jobs/base.html') - self.assertTemplateUsed(response, 'jobs/job_list.html') + self.assertTemplateUsed(response, "jobs/base.html") + self.assertTemplateUsed(response, "jobs/job_list.html") - url = reverse('jobs:job_list_type', kwargs={'slug': self.job_type.slug}) + url = reverse("jobs:job_list_type", kwargs={"slug": self.job_type.slug}) response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 1) - self.assertTemplateUsed(response, 'jobs/base.html') - self.assertTemplateUsed(response, 'jobs/job_list.html') + self.assertEqual(len(response.context["object_list"]), 1) + self.assertTemplateUsed(response, "jobs/base.html") + self.assertTemplateUsed(response, "jobs/job_list.html") - url = reverse('jobs:job_list_category', kwargs={'slug': self.job_category.slug}) + url = reverse("jobs:job_list_category", kwargs={"slug": self.job_category.slug}) response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 1) - self.assertTemplateUsed(response, 'jobs/base.html') - self.assertTemplateUsed(response, 'jobs/job_list.html') + self.assertEqual(len(response.context["object_list"]), 1) + self.assertTemplateUsed(response, "jobs/base.html") + self.assertTemplateUsed(response, "jobs/job_list.html") - url = reverse('jobs:job_list_location', kwargs={'slug': self.job.location_slug}) + url = reverse("jobs:job_list_location", kwargs={"slug": self.job.location_slug}) response = self.client.get(url) - self.assertEqual(len(response.context['object_list']), 1) + self.assertEqual(len(response.context["object_list"]), 1) self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'jobs/base.html') - self.assertTemplateUsed(response, 'jobs/job_list.html') + self.assertTemplateUsed(response, "jobs/base.html") + self.assertTemplateUsed(response, "jobs/job_list.html") def test_job_list_mine(self): - url = reverse('jobs:job_list_mine') + url = reverse("jobs:job_list_mine") response = self.client.get(url) - self.assertRedirects(response, '{}?next={}'.format(reverse('account_login'), url)) + self.assertRedirects(response, "{}?next={}".format(reverse("account_login"), url)) - username = 'kevinarnold' - email = 'kevinarnold@example.com' - password = 'secret' + username = "kevinarnold" + email = "kevinarnold@example.com" + password = "secret" User = get_user_model() creator = User.objects.create_user(username, email, password) self.job = ApprovedJobFactory( - description='My job listing', + description="My job listing", category=self.job_category, - city='Memphis', - region='TN', - country='USA', - email='hr@company.com', + city="Memphis", + region="TN", + country="USA", + email="hr@company.com", creator=creator, - is_featured=True + is_featured=True, ) self.client.login(username=username, password=password) response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 1) - self.assertEqual(response.context['jobs_count'], 2) - self.assertTemplateUsed(response, 'jobs/base.html') - self.assertTemplateUsed(response, 'jobs/job_list.html') + self.assertEqual(len(response.context["object_list"]), 1) + self.assertEqual(response.context["jobs_count"], 2) + self.assertTemplateUsed(response, "jobs/base.html") + self.assertTemplateUsed(response, "jobs/job_list.html") def test_job_mine_remove(self): - url = reverse('jobs:job_list_mine') + url = reverse("jobs:job_list_mine") - self.client.login(username=self.user.username, password='password') + self.client.login(username=self.user.username, password="password") response = self.client.get(url) self.assertEqual(response.status_code, 200) - job = response.context['object_list'][0] + job = response.context["object_list"][0] self.assertNotEqual(job.status, job.STATUS_REMOVED) - url = reverse('jobs:job_remove', kwargs={'pk': job.pk}) + url = reverse("jobs:job_remove", kwargs={"pk": job.pk}) response = self.client.get(url) - self.assertRedirects(response, reverse('jobs:job_list_mine')) + self.assertRedirects(response, reverse("jobs:job_list_mine")) job.refresh_from_db() self.assertEqual(job.status, job.STATUS_REMOVED) def test_job_mine_remove_404(self): - url = reverse('jobs:job_list_mine') + url = reverse("jobs:job_list_mine") - self.client.login(username=self.user2.username, password='password') + self.client.login(username=self.user2.username, password="password") response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 0) + self.assertEqual(len(response.context["object_list"]), 0) self.assertNotEqual(self.job.status, self.job.STATUS_REMOVED) - url = reverse('jobs:job_remove', kwargs={'pk': self.job.pk}) + url = reverse("jobs:job_remove", kwargs={"pk": self.job.pk}) response = self.client.get(url) - self.assertRedirects(response, reverse('jobs:job_list_mine')) + self.assertRedirects(response, reverse("jobs:job_list_mine")) self.assertNotEqual(self.job.status, self.job.STATUS_REMOVED) def test_job_mine_remove_post_request(self): - url = reverse('jobs:job_remove', kwargs={'pk': self.job.pk}) + url = reverse("jobs:job_remove", kwargs={"pk": self.job.pk}) - self.client.login(username=self.user.username, password='password') + self.client.login(username=self.user.username, password="password") response = self.client.post(url) self.assertEqual(response.status_code, 405) def test_job_mine_remove_login(self): - url = reverse('jobs:job_remove', kwargs={'pk': self.job.pk}) + url = reverse("jobs:job_remove", kwargs={"pk": self.job.pk}) response = self.client.get(url) - self.assertRedirects( - response, - '/accounts/login/?next=/jobs/%d/remove/' % self.job.pk - ) + self.assertRedirects(response, "/accounts/login/?next=/jobs/%d/remove/" % self.job.pk) def test_disallow_editing_approved_jobs(self): - self.client.login(username=self.user.username, password='password') - url = reverse('jobs:job_edit', kwargs={'pk': self.job.pk}) + self.client.login(username=self.user.username, password="password") + url = reverse("jobs:job_edit", kwargs={"pk": self.job.pk}) response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_disallow_previewing_approved_jobs(self): - self.client.login(username=self.user.username, password='password') - url = reverse('jobs:job_preview', kwargs={'pk': self.job.pk}) + self.client.login(username=self.user.username, password="password") + url = reverse("jobs:job_preview", kwargs={"pk": self.job.pk}) response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_job_edit(self): - username = 'kevinarnold' - email = 'kevinarnold@example.com' - password = 'secret' + username = "kevinarnold" + email = "kevinarnold@example.com" + password = "secret" User = get_user_model() creator = User.objects.create_user(username, email, password) job = DraftJobFactory( - description='My job listing', + description="My job listing", category=self.job_category, - city='Memphis', - region='TN', - country='USA', - email='hr@company.com', + city="Memphis", + region="TN", + country="USA", + email="hr@company.com", creator=creator, - is_featured=True + is_featured=True, ) job.job_types.add(self.job_type) self.client.login(username=username, password=password) - url = reverse('jobs:job_edit', kwargs={'pk': job.pk}) + url = reverse("jobs:job_edit", kwargs={"pk": job.pk}) response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'jobs/base.html') + self.assertTemplateUsed(response, "jobs/base.html") # Edit the job. Job.editable should return True to be # able to edit a job. - form = response.context['form'] + form = response.context["form"] data = form.initial # Quoted from Django 1.10 release notes: # Private API django.forms.models.model_to_dict() returns a # queryset rather than a list of primary keys for ManyToManyFields. - data['job_types'] = [self.job_type.pk] - data['description'] = 'Lorem ipsum dolor sit amet' + data["job_types"] = [self.job_type.pk] + data["description"] = "Lorem ipsum dolor sit amet" response = self.client.post(url, data) - self.assertRedirects(response, '/jobs/%d/preview/' % job.pk) + self.assertRedirects(response, "/jobs/%d/preview/" % job.pk) edited_job = Job.objects.get(pk=job.pk) - self.assertEqual(edited_job.description.raw, 'Lorem ipsum dolor sit amet') + self.assertEqual(edited_job.description.raw, "Lorem ipsum dolor sit amet") self.client.logout() response = self.client.get(url) self.assertEqual(response.status_code, 302) - self.assertRedirects(response, '/accounts/login/?next=/jobs/%d/edit/' % job.pk) + self.assertRedirects(response, "/accounts/login/?next=/jobs/%d/edit/" % job.pk) # Staffs can see the edit form. - self.client.login(username=self.staff.username, password='password') + self.client.login(username=self.staff.username, password="password") response = self.client.get(url) self.assertEqual(response.status_code, 200) @@ -238,8 +233,8 @@ def test_job_detail(self): response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(response.context['jobs_count'], 1) - self.assertTemplateUsed(response, 'jobs/base.html') + self.assertEqual(response.context["jobs_count"], 1) + self.assertTemplateUsed(response, "jobs/base.html") # Logout users cannot see the job details. url = self.job_draft.get_absolute_url() @@ -247,18 +242,18 @@ def test_job_detail(self): self.assertEqual(response.status_code, 404) # Creator can see their own jobs no matter the status. - self.client.login(username=self.user.username, password='password') + self.client.login(username=self.user.username, password="password") response = self.client.get(url) self.assertEqual(response.status_code, 200) # And other users can see other users approved jobs. self.client.logout() - self.client.login(username=self.user2.username, password='password') + self.client.login(username=self.user2.username, password="password") response = self.client.get(self.job.get_absolute_url()) self.assertEqual(response.status_code, 200) # Try to reach a job that doesn't exist. - url = reverse('jobs:job_detail', kwargs={'pk': 999999}) + url = reverse("jobs:job_detail", kwargs={"pk": 999999}) response = self.client.get(url) self.assertEqual(response.status_code, 404) @@ -275,7 +270,7 @@ def test_job_detail_security(self): self.assertEqual(response.status_code, 404) # Staff can see everything - self.client.login(username=self.staff.username, password='password') + self.client.login(username=self.staff.username, password="password") response = self.client.get(self.job.get_absolute_url()) self.assertEqual(response.status_code, 200) @@ -284,276 +279,244 @@ def test_job_detail_security(self): def test_job_create(self): mail.outbox = [] - url = reverse('jobs:job_create') + url = reverse("jobs:job_create") response = self.client.get(url) self.assertEqual(response.status_code, 302) - self.assertRedirects(response, '/accounts/login/?next=/jobs/create/') + self.assertRedirects(response, "/accounts/login/?next=/jobs/create/") post_data = { - 'category': self.job_category.pk, - 'job_types': [self.job_type.pk], - 'company_name': 'Some Company', - 'company_description': 'Some Description', - 'job_title': 'Test Job', - 'city': 'San Diego', - 'region': 'CA', - 'country': 'USA', - 'description': 'Lorem ipsum dolor sit amet', - 'requirements': 'Some requirements', - 'email': 'hr@company.com', - 'url': 'https://jobs.company.com', + "category": self.job_category.pk, + "job_types": [self.job_type.pk], + "company_name": "Some Company", + "company_description": "Some Description", + "job_title": "Test Job", + "city": "San Diego", + "region": "CA", + "country": "USA", + "description": "Lorem ipsum dolor sit amet", + "requirements": "Some requirements", + "email": "hr@company.com", + "url": "https://jobs.company.com", } # Check that anonymous posting is not allowed. See #852. response = self.client.post(url, post_data) self.assertEqual(response.status_code, 302) - self.assertRedirects(response, '/accounts/login/?next=/jobs/create/') + self.assertRedirects(response, "/accounts/login/?next=/jobs/create/") # Now test job submitted by logged in user - post_data['company_name'] = 'Other Studio' + post_data["company_name"] = "Other Studio" - username = 'kevinarnold' - email = 'kevinarnold@example.com' - password = 'secret' + username = "kevinarnold" + email = "kevinarnold@example.com" + password = "secret" User = get_user_model() creator = User.objects.create_user(username, email, password) - self.client.login(username=creator.username, password='secret') + self.client.login(username=creator.username, password="secret") response = self.client.post(url, post_data, follow=True) # Job was saved in draft mode - jobs = Job.objects.filter(company_name='Other Studio') + jobs = Job.objects.filter(company_name="Other Studio") self.assertEqual(len(jobs), 1) job = jobs[0] - preview_url = reverse('jobs:job_preview', kwargs={'pk': job.pk}) + preview_url = reverse("jobs:job_preview", kwargs={"pk": job.pk}) self.assertRedirects(response, preview_url) self.assertNotEqual(job.created, None) self.assertNotEqual(job.updated, None) self.assertEqual(job.creator, creator) - self.assertEqual(job.status, 'draft') + self.assertEqual(job.status, "draft") self.assertEqual(len(mail.outbox), 0) # Submit again to save - response = self.client.post(preview_url, {'action': 'review'}) + response = self.client.post(preview_url, {"action": "review"}) # Job was now moved to review status job = Job.objects.get(pk=job.pk) - self.assertEqual(job.status, 'review') + self.assertEqual(job.status, "review") # One email was sent self.assertEqual(len(mail.outbox), 1) - self.assertEqual( - mail.outbox[0].subject, - f"Job Submitted for Approval: {job.display_name}" - ) + self.assertEqual(mail.outbox[0].subject, f"Job Submitted for Approval: {job.display_name}") del mail.outbox[:] def test_job_preview_404(self): - url = reverse('jobs:job_preview', kwargs={'pk': 9999999}) + url = reverse("jobs:job_preview", kwargs={"pk": 9999999}) # /jobs//preview/ requires to be logged in. - self.client.login(username=self.user.username, password='password') + self.client.login(username=self.user.username, password="password") response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_job_create_prepopulate_email(self): - create_url = reverse('jobs:job_create') + create_url = reverse("jobs:job_create") user_data = { - 'username': 'phrasebook', - 'email': 'hungarian@example.com', - 'password': 'hovereel', + "username": "phrasebook", + "email": "hungarian@example.com", + "password": "hovereel", } User = get_user_model() creator = User.objects.create_user(**user_data) # Logged in, email address is prepopulated. - self.client.login(username=user_data['username'], - password=user_data['password']) + self.client.login(username=user_data["username"], password=user_data["password"]) response = self.client.get(create_url) def test_job_types(self): - job_type2 = JobTypeFactory( - name='Senior Developer', - slug='senior-developer' - ) + job_type2 = JobTypeFactory(name="Senior Developer", slug="senior-developer") - url = reverse('jobs:job_types') + url = reverse("jobs:job_types") response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertIn(self.job_type, response.context['types']) - self.assertNotIn(job_type2, response.context['types']) + self.assertIn(self.job_type, response.context["types"]) + self.assertNotIn(job_type2, response.context["types"]) def test_job_categories(self): - job_category2 = JobCategoryFactory( - name='Web Development', - slug='web-development' - ) + job_category2 = JobCategoryFactory(name="Web Development", slug="web-development") - url = reverse('jobs:job_categories') + url = reverse("jobs:job_categories") response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertIn(self.job_category, response.context['categories']) - self.assertNotIn(job_category2, response.context['categories']) + self.assertIn(self.job_category, response.context["categories"]) + self.assertNotIn(job_category2, response.context["categories"]) def test_job_locations(self): job2 = ReviewJobFactory( - description='Lorem ipsum dolor sit amet', + description="Lorem ipsum dolor sit amet", category=self.job_category, - city='Lawrence', - region='KS', - country='USA', - email='hr@company.com', + city="Lawrence", + region="KS", + country="USA", + email="hr@company.com", ) job2.job_types.add(self.job_type) - url = reverse('jobs:job_locations') + url = reverse("jobs:job_locations") response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertIn(self.job, response.context['jobs']) - self.assertNotIn(job2, response.context['jobs']) + self.assertIn(self.job, response.context["jobs"]) + self.assertNotIn(job2, response.context["jobs"]) content = str(response.content) - self.assertIn('Memphis', content) - self.assertNotIn('Lawrence', content) + self.assertIn("Memphis", content) + self.assertNotIn("Lawrence", content) def test_job_telecommute(self): - url = reverse('jobs:job_telecommute') + url = reverse("jobs:job_telecommute") response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertIn(self.job, response.context['jobs']) + self.assertIn(self.job, response.context["jobs"]) def test_job_display_name(self): - self.assertEqual(self.job.display_name, - f"{self.job.job_title}, {self.job.company_name}") + self.assertEqual(self.job.display_name, f"{self.job.job_title}, {self.job.company_name}") - self.job.company_name = 'ABC' - self.assertEqual(self.job.display_name, - f"{self.job.job_title}, {self.job.company_name}") + self.job.company_name = "ABC" + self.assertEqual(self.job.display_name, f"{self.job.job_title}, {self.job.company_name}") - self.job.company_name = '' - self.assertEqual(self.job.display_name, - f"{self.job.job_title}, {self.job.company_name}") + self.job.company_name = "" + self.assertEqual(self.job.display_name, f"{self.job.job_title}, {self.job.company_name}") def test_job_display_about(self): - self.job.company_description.raw = 'XYZ' + self.job.company_description.raw = "XYZ" self.assertEqual(self.job.display_description.raw, self.job.company_description.raw) - self.job.company_description = ' ' + self.job.company_description = " " self.assertEqual(self.job.display_description.raw, self.job.company_description.raw) def test_job_list_type_404(self): - url = reverse('jobs:job_list_type', kwargs={'slug': 'invalid-type'}) + url = reverse("jobs:job_list_type", kwargs={"slug": "invalid-type"}) response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_job_list_category_404(self): - url = reverse('jobs:job_list_category', kwargs={'slug': 'invalid-type'}) + url = reverse("jobs:job_list_category", kwargs={"slug": "invalid-type"}) response = self.client.get(url) self.assertEqual(response.status_code, 404) class JobsReviewTests(TestCase): def setUp(self): + self.super_username = "kevinarnold" + self.super_email = "kevinarnold@example.com" + self.super_password = "secret" - self.super_username = 'kevinarnold' - self.super_email = 'kevinarnold@example.com' - self.super_password = 'secret' + self.creator_username = "johndoe" + self.creator_email = "johndoe@example.com" + self.creator_password = "secret" + self.contact = "John Doe" - self.creator_username = 'johndoe' - self.creator_email = 'johndoe@example.com' - self.creator_password = 'secret' - self.contact = 'John Doe' - - self.another_username = 'another' - self.another_email = 'another@example.com' - self.another_password = 'secret' + self.another_username = "another" + self.another_email = "another@example.com" + self.another_password = "secret" User = get_user_model() - self.creator = User.objects.create_user( - self.creator_username, - self.creator_email, - self.creator_password - ) + self.creator = User.objects.create_user(self.creator_username, self.creator_email, self.creator_password) - self.superuser = User.objects.create_superuser( - self.super_username, - self.super_email, - self.super_password - ) + self.superuser = User.objects.create_superuser(self.super_username, self.super_email, self.super_password) - self.another = User.objects.create_user( - self.another_username, - self.another_email, - self.another_password - ) + self.another = User.objects.create_user(self.another_username, self.another_email, self.another_password) - self.job_category = JobCategoryFactory( - name='Game Production', - slug='game-production' - ) + self.job_category = JobCategoryFactory(name="Game Production", slug="game-production") - self.job_type = JobTypeFactory( - name='FrontEnd Developer', - slug='frontend-developer' - ) + self.job_type = JobTypeFactory(name="FrontEnd Developer", slug="frontend-developer") self.job1 = ReviewJobFactory( - company_name='Kulfun Games', - description='Lorem ipsum dolor sit amet', + company_name="Kulfun Games", + description="Lorem ipsum dolor sit amet", category=self.job_category, - city='Memphis', - region='TN', - country='USA', + city="Memphis", + region="TN", + country="USA", email=self.creator.email, creator=self.creator, - contact=self.contact + contact=self.contact, ) self.job1.job_types.add(self.job_type) self.job2 = ReviewJobFactory( - company_name='Kulfun Games', - description='Lorem ipsum dolor sit amet', + company_name="Kulfun Games", + description="Lorem ipsum dolor sit amet", category=self.job_category, - city='Memphis', - region='TN', - country='USA', + city="Memphis", + region="TN", + country="USA", email=self.creator.email, creator=self.creator, - contact=self.contact + contact=self.contact, ) self.job2.job_types.add(self.job_type) self.job3 = ReviewJobFactory( - company_name='Kulfun Games', - description='Lorem ipsum dolor sit amet', + company_name="Kulfun Games", + description="Lorem ipsum dolor sit amet", category=self.job_category, - city='Memphis', - region='TN', - country='USA', + city="Memphis", + region="TN", + country="USA", email=self.creator.email, creator=self.creator, - contact=self.contact + contact=self.contact, ) self.job3.job_types.add(self.job_type) def test_moderate(self): - url = reverse('jobs:job_moderate') + url = reverse("jobs:job_moderate") job = ApprovedJobFactory() response = self.client.get(url) - self.assertRedirects(response, '{}?next={}'.format(reverse('account_login'), url)) + self.assertRedirects(response, "{}?next={}".format(reverse("account_login"), url)) self.client.login(username=self.another_username, password=self.another_password) response = self.client.get(url) self.assertEqual(response.status_code, 403) - self.assertTemplateUsed(response, '403.html') + self.assertTemplateUsed(response, "403.html") self.client.logout() self.client.login(username=self.super_username, password=self.super_password) @@ -561,36 +524,36 @@ def test_moderate(self): response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 1) - self.assertIn(job, response.context['object_list']) - self.assertNotIn(self.job1, response.context['object_list']) + self.assertEqual(len(response.context["object_list"]), 1) + self.assertIn(job, response.context["object_list"]) + self.assertNotIn(self.job1, response.context["object_list"]) def test_moderate_search(self): - url = reverse('jobs:job_moderate') + url = reverse("jobs:job_moderate") - job = ApprovedJobFactory(job_title='foo') - job2 = ApprovedJobFactory(job_title='bar foo') + job = ApprovedJobFactory(job_title="foo") + job2 = ApprovedJobFactory(job_title="bar foo") self.client.login(username=self.super_username, password=self.super_password) - response = self.client.get(url, {'term': 'foo'}) + response = self.client.get(url, {"term": "foo"}) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 2) - self.assertIn(job, response.context['object_list']) - self.assertIn(job2, response.context['object_list']) + self.assertEqual(len(response.context["object_list"]), 2) + self.assertIn(job, response.context["object_list"]) + self.assertIn(job2, response.context["object_list"]) def test_job_review(self): # FIXME: refactor to separate tests cases for clarity? mail.outbox = [] - url = reverse('jobs:job_review') + url = reverse("jobs:job_review") response = self.client.get(url) - self.assertRedirects(response, '{}?next={}'.format(reverse('account_login'), url)) + self.assertRedirects(response, "{}?next={}".format(reverse("account_login"), url)) self.client.login(username=self.another_username, password=self.another_password) response = self.client.get(url) self.assertEqual(response.status_code, 403) - self.assertTemplateUsed(response, '403.html') + self.assertTemplateUsed(response, "403.html") self.client.logout() self.client.login(username=self.super_username, password=self.super_password) @@ -598,68 +561,68 @@ def test_job_review(self): response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 3) - self.assertIn(self.job1, response.context['object_list']) - self.assertIn(self.job2, response.context['object_list']) - self.assertIn(self.job3, response.context['object_list']) + self.assertEqual(len(response.context["object_list"]), 3) + self.assertIn(self.job1, response.context["object_list"]) + self.assertIn(self.job2, response.context["object_list"]) + self.assertIn(self.job3, response.context["object_list"]) # no email notifications sent before offer is approved self.assertEqual(len(mail.outbox), 0) - self.client.post(url, data={'job_id': self.job1.pk, 'action': 'approve'}) + self.client.post(url, data={"job_id": self.job1.pk, "action": "approve"}) j1 = Job.objects.get(pk=self.job1.pk) self.assertEqual(j1.status, Job.STATUS_APPROVED) # exactly one approval notification email should sent # to the offer creator self.assertEqual(len(mail.outbox), 1) message = mail.outbox[0] - self.assertEqual(message.to, [self.creator.email, 'jobs@python.org']) + self.assertEqual(message.to, [self.creator.email, "jobs@python.org"]) self.assertIn(self.contact, message.body) mail.outbox = [] # no email notifications sent before offer is rejected self.assertEqual(len(mail.outbox), 0) - self.client.post(url, data={'job_id': self.job2.pk, 'action': 'reject'}) + self.client.post(url, data={"job_id": self.job2.pk, "action": "reject"}) j2 = Job.objects.get(pk=self.job2.pk) self.assertEqual(j2.status, Job.STATUS_REJECTED) # exactly one rejection notification email should sent # to the offer creator self.assertEqual(len(mail.outbox), 1) message = mail.outbox[0] - self.assertEqual(message.to, [self.creator.email, 'jobs@python.org']) + self.assertEqual(message.to, [self.creator.email, "jobs@python.org"]) self.assertIn(self.contact, message.body) mail.outbox = [] - response = self.client.post(url, data={'job_id': self.job2.pk, 'action': 'archive'}) - self.assertRedirects(response, reverse('jobs:job_review')) + response = self.client.post(url, data={"job_id": self.job2.pk, "action": "archive"}) + self.assertRedirects(response, reverse("jobs:job_review")) j2 = Job.objects.get(pk=self.job2.pk) self.assertEqual(j2.status, Job.STATUS_ARCHIVED) - self.client.post(url, data={'job_id': self.job3.pk, 'action': 'remove'}) + self.client.post(url, data={"job_id": self.job3.pk, "action": "remove"}) j3 = Job.objects.get(pk=self.job3.pk) self.assertEqual(j3.status, Job.STATUS_REMOVED) - response = self.client.post(url, data={'job_id': 999999, 'action': 'approve'}) + response = self.client.post(url, data={"job_id": 999999, "action": "approve"}) self.assertEqual(response.status_code, 302) # Invalid action should raise a 404 error. - response = self.client.post(url, data={'job_id': self.job2.pk, 'action': 'invalid'}) + response = self.client.post(url, data={"job_id": self.job2.pk, "action": "invalid"}) self.assertEqual(response.status_code, 404) def test_job_comment(self): mail.outbox = [] self.client.login(username=self.creator_username, password=self.creator_password) - url = reverse('jobs:job_review_comment_create') + url = reverse("jobs:job_review_comment_create") form_data = { - 'job': self.job1.pk, - 'comment': 'Lorem ispum', + "job": self.job1.pk, + "comment": "Lorem ispum", } self.assertEqual(len(mail.outbox), 0) response = self.client.post(url, form_data) self.assertEqual(response.status_code, 302) self.assertEqual(len(mail.outbox), 1) # We should only send an email to jobs@p.o. - self.assertEqual(mail.outbox[0].to, ['jobs@python.org']) - self.assertIn('Dear Python Job Board Admin,', mail.outbox[0].body) + self.assertEqual(mail.outbox[0].to, ["jobs@python.org"]) + self.assertIn("Dear Python Job Board Admin,", mail.outbox[0].body) self.client.logout() # Send a comment as a jobs board admin. @@ -670,19 +633,16 @@ def test_job_comment(self): self.assertEqual(response.status_code, 302) self.assertEqual(len(mail.outbox), 1) # We should send an email to both jobs@p.o and job submitter. - self.assertEqual(mail.outbox[0].to, ['jobs@python.org', self.creator_email]) - self.assertIn( - 'There is a new review comment available for your job posting.', - mail.outbox[0].body - ) + self.assertEqual(mail.outbox[0].to, ["jobs@python.org", self.creator_email]) + self.assertIn("There is a new review comment available for your job posting.", mail.outbox[0].body) def test_job_comment_401(self): mail.outbox = [] self.client.login(username=self.another_username, password=self.another_password) - url = reverse('jobs:job_review_comment_create') + url = reverse("jobs:job_review_comment_create") form_data = { - 'job': self.job1.pk, - 'comment': 'Foooo', + "job": self.job1.pk, + "comment": "Foooo", } self.assertEqual(len(mail.outbox), 0) response = self.client.post(url, form_data) @@ -692,10 +652,10 @@ def test_job_comment_401(self): def test_job_comment_401_approve(self): mail.outbox = [] self.client.login(username=self.creator_username, password=self.creator_password) - url = reverse('jobs:job_review_comment_create') + url = reverse("jobs:job_review_comment_create") form_data = { - 'job': self.job1.pk, - 'action': 'approve', + "job": self.job1.pk, + "action": "approve", } self.assertEqual(len(mail.outbox), 0) response = self.client.post(url, form_data) @@ -705,13 +665,13 @@ def test_job_comment_401_approve(self): def test_job_comment_approve(self): mail.outbox = [] self.client.login(username=self.super_username, password=self.super_password) - url = reverse('jobs:job_review_comment_create') + url = reverse("jobs:job_review_comment_create") form_data = { - 'job': self.job1.pk, - 'action': 'approve', + "job": self.job1.pk, + "action": "approve", } self.assertEqual(len(mail.outbox), 0) response = self.client.post(url, form_data) self.assertEqual(response.status_code, 302) self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].to, [self.creator.email, 'jobs@python.org']) + self.assertEqual(mail.outbox[0].to, [self.creator.email, "jobs@python.org"]) diff --git a/jobs/urls.py b/jobs/urls.py index 319ec98c3..49a4cf97f 100644 --- a/jobs/urls.py +++ b/jobs/urls.py @@ -4,25 +4,25 @@ from . import feeds from django.urls import path -app_name = 'jobs' +app_name = "jobs" urlpatterns = [ - path('', views.JobList.as_view(), name='job_list'), - path('feed/rss/', feeds.JobFeed(), name='job_rss'), - path('create/', views.JobCreate.as_view(), name='job_create'), - path('create-review-comment/', views.JobReviewCommentCreate.as_view(), name='job_review_comment_create'), - path('mine/', views.JobListMine.as_view(), name='job_list_mine'), - path('review/', views.JobReview.as_view(), name='job_review'), - path('moderate/', views.JobModerateList.as_view(), name='job_moderate'), - path('thanks/', TemplateView.as_view(template_name="jobs/job_thanks.html"), name='job_thanks'), - path('location/telecommute/', views.JobTelecommute.as_view(), name='job_telecommute'), - path('location//', views.JobListLocation.as_view(), name='job_list_location'), - path('type//', views.JobListType.as_view(), name='job_list_type'), - path('category//', views.JobListCategory.as_view(), name='job_list_category'), - path('locations/', views.JobLocations.as_view(), name='job_locations'), - path('types/', views.JobTypes.as_view(), name='job_types'), - path('categories/', views.JobCategories.as_view(), name='job_categories'), - path('/edit/', views.JobEdit.as_view(), name='job_edit'), - path('/preview/', views.JobPreview.as_view(), name='job_preview'), - path('/remove/', views.JobRemove.as_view(), name='job_remove'), - path('/', views.JobDetail.as_view(), name='job_detail'), + path("", views.JobList.as_view(), name="job_list"), + path("feed/rss/", feeds.JobFeed(), name="job_rss"), + path("create/", views.JobCreate.as_view(), name="job_create"), + path("create-review-comment/", views.JobReviewCommentCreate.as_view(), name="job_review_comment_create"), + path("mine/", views.JobListMine.as_view(), name="job_list_mine"), + path("review/", views.JobReview.as_view(), name="job_review"), + path("moderate/", views.JobModerateList.as_view(), name="job_moderate"), + path("thanks/", TemplateView.as_view(template_name="jobs/job_thanks.html"), name="job_thanks"), + path("location/telecommute/", views.JobTelecommute.as_view(), name="job_telecommute"), + path("location//", views.JobListLocation.as_view(), name="job_list_location"), + path("type//", views.JobListType.as_view(), name="job_list_type"), + path("category//", views.JobListCategory.as_view(), name="job_list_category"), + path("locations/", views.JobLocations.as_view(), name="job_locations"), + path("types/", views.JobTypes.as_view(), name="job_types"), + path("categories/", views.JobCategories.as_view(), name="job_categories"), + path("/edit/", views.JobEdit.as_view(), name="job_edit"), + path("/preview/", views.JobPreview.as_view(), name="job_preview"), + path("/remove/", views.JobRemove.as_view(), name="job_remove"), + path("/", views.JobDetail.as_view(), name="job_detail"), ] diff --git a/jobs/views.py b/jobs/views.py index 9e781a185..3c850fbe8 100644 --- a/jobs/views.py +++ b/jobs/views.py @@ -47,19 +47,23 @@ class JobMixin: def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - active_locations = Job.objects.visible().distinct( - 'location_slug' - ).order_by( - 'location_slug', + active_locations = ( + Job.objects.visible() + .distinct("location_slug") + .order_by( + "location_slug", + ) ) - context.update({ - 'jobs_count': Job.objects.visible().count(), - 'active_types': JobType.objects.with_active_jobs(), - 'active_categories': JobCategory.objects.with_active_jobs(), - 'active_locations': active_locations, - 'jobs_board_admin': self.has_jobs_board_admin_access(), - }) + context.update( + { + "jobs_count": Job.objects.visible().count(), + "active_types": JobType.objects.with_active_jobs(), + "active_categories": JobCategory.objects.with_active_jobs(), + "active_locations": active_locations, + "jobs_board_admin": self.has_jobs_board_admin_access(), + } + ) return context @@ -68,7 +72,7 @@ def has_jobs_board_admin_access(self): # with current staff members. if self.request.user.is_staff or self.request.user.is_superuser: return True - user_groups = self.request.user.groups.values_list('name', flat=True) + user_groups = self.request.user.groups.values_list("name", flat=True) return JobBoardAdminRequiredMixin.group_required in user_groups @@ -88,126 +92,119 @@ def get_queryset(self): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['mine_listing'] = True + context["mine_listing"] = True return context class JobListType(JobTypeMenu, JobMixin, ListView): paginate_by = 25 - template_name = 'jobs/job_type_list.html' + template_name = "jobs/job_type_list.html" def get_queryset(self): - self.current_type = get_object_or_404(JobType, - slug=self.kwargs['slug']) - return Job.objects.visible().select_related().filter( - job_types__slug=self.kwargs['slug']) + self.current_type = get_object_or_404(JobType, slug=self.kwargs["slug"]) + return Job.objects.visible().select_related().filter(job_types__slug=self.kwargs["slug"]) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['current_type'] = self.current_type + context["current_type"] = self.current_type return context class JobListCategory(JobCategoryMenu, JobMixin, ListView): paginate_by = 25 - template_name = 'jobs/job_category_list.html' + template_name = "jobs/job_category_list.html" def get_queryset(self): - self.current_category = get_object_or_404(JobCategory, - slug=self.kwargs['slug']) - return Job.objects.visible().select_related().filter( - category__slug=self.kwargs['slug']) + self.current_category = get_object_or_404(JobCategory, slug=self.kwargs["slug"]) + return Job.objects.visible().select_related().filter(category__slug=self.kwargs["slug"]) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['current_category'] = self.current_category + context["current_category"] = self.current_category return context class JobListLocation(JobLocationMenu, JobMixin, ListView): paginate_by = 25 - template_name = 'jobs/job_location_list.html' + template_name = "jobs/job_location_list.html" def get_queryset(self): - return Job.objects.visible().select_related().filter( - location_slug=self.kwargs['slug']) + return Job.objects.visible().select_related().filter(location_slug=self.kwargs["slug"]) class JobTypes(JobTypeMenu, JobMixin, ListView): - """ View to simply list JobType instances that have current jobs """ + """View to simply list JobType instances that have current jobs""" + template_name = "jobs/job_types.html" - queryset = JobType.objects.with_active_jobs().order_by('name') - context_object_name = 'types' + queryset = JobType.objects.with_active_jobs().order_by("name") + context_object_name = "types" class JobCategories(JobCategoryMenu, JobMixin, ListView): - """ View to simply list JobCategory instances that have current jobs """ + """View to simply list JobCategory instances that have current jobs""" + template_name = "jobs/job_categories.html" - queryset = JobCategory.objects.with_active_jobs().order_by('name') - context_object_name = 'categories' + queryset = JobCategory.objects.with_active_jobs().order_by("name") + context_object_name = "categories" class JobLocations(JobLocationMenu, JobMixin, TemplateView): - """ View to simply list distinct Countries that have current jobs """ + """View to simply list distinct Countries that have current jobs""" + template_name = "jobs/job_locations.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['jobs'] = Job.objects.visible().distinct( - 'country', 'city' - ).order_by( - 'country', 'city' - ) + context["jobs"] = Job.objects.visible().distinct("country", "city").order_by("country", "city") return context class JobTelecommute(JobLocationMenu, JobList): - """ Specific view for telecommute jobs """ - template_name = 'jobs/job_telecommute_list.html' + """Specific view for telecommute jobs""" + + template_name = "jobs/job_telecommute_list.html" def get_queryset(self): - return super().get_queryset().visible().select_related().filter( - telecommuting=True - ) + return super().get_queryset().visible().select_related().filter(telecommuting=True) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['jobs_count'] = len(self.object_list) - context['jobs'] = self.object_list + context["jobs_count"] = len(self.object_list) + context["jobs"] = self.object_list return context class JobReview(LoginRequiredMixin, JobBoardAdminRequiredMixin, JobMixin, ListView): - template_name = 'jobs/job_review.html' + template_name = "jobs/job_review.html" paginate_by = 20 - redirect_url = 'jobs:job_review' + redirect_url = "jobs:job_review" def get_queryset(self): return Job.objects.review() def post(self, request): try: - job = Job.objects.get(id=request.POST['job_id']) - action = request.POST['action'] + job = Job.objects.get(id=request.POST["job_id"]) + action = request.POST["action"] except (KeyError, Job.DoesNotExist): - return redirect('jobs:job_review') + return redirect("jobs:job_review") - if action == 'approve': + if action == "approve": job.approve(request.user) messages.add_message(self.request, messages.SUCCESS, "'%s' approved." % job) - elif action == 'reject': + elif action == "reject": job.reject(request.user) messages.add_message(self.request, messages.SUCCESS, "'%s' rejected." % job) - elif action == 'remove': + elif action == "remove": job.status = Job.STATUS_REMOVED job.save() messages.add_message(self.request, messages.SUCCESS, "'%s' removed." % job) - elif action == 'archive': + elif action == "archive": job.status = Job.STATUS_ARCHIVED job.save() messages.add_message(self.request, messages.SUCCESS, "'%s' archived." % job) @@ -218,41 +215,39 @@ def post(self, request): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['mode'] = 'review' + context["mode"] = "review" return context class JobRemove(LoginRequiredMixin, View): - def get(self, request, pk): try: job = Job.objects.get(id=pk, creator=request.user) except Job.DoesNotExist: - return redirect('jobs:job_list_mine') + return redirect("jobs:job_list_mine") job.status = Job.STATUS_REMOVED job.save() messages.add_message(request, messages.SUCCESS, "'%s' removed." % job) - return redirect('jobs:job_list_mine') + return redirect("jobs:job_list_mine") class JobModerateList(JobReview): - redirect_url = 'jobs:job_moderate' + redirect_url = "jobs:job_moderate" def get_queryset(self): queryset = Job.objects.moderate() - q = self.request.GET.get('q') + q = self.request.GET.get("q") if q is not None: return queryset.filter(job_title__icontains=q) return queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['mode'] = 'moderate' + context["mode"] = "moderate" return context class JobDetail(JobMixin, DetailView): - def get_queryset(self): queryset = Job.objects.select_related() if self.has_jobs_board_admin_access(): @@ -265,21 +260,20 @@ def get_queryset(self): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['category_jobs'] = self.object.category.jobs.select_related('category')[:5] - context['user_can_edit'] = ( - self.object.creator == self.request.user or - self.has_jobs_board_admin_access() + context["category_jobs"] = self.object.category.jobs.select_related("category")[:5] + context["user_can_edit"] = ( + self.object.creator == self.request.user or self.has_jobs_board_admin_access() ) and self.object.editable - context['job_review_form'] = JobReviewCommentForm(initial={'job': self.object}) + context["job_review_form"] = JobReviewCommentForm(initial={"job": self.object}) return context class JobPreview(LoginRequiredMixin, JobDetail, UpdateView): - template_name = 'jobs/job_detail.html' + template_name = "jobs/job_detail.html" form_class = JobForm def get_success_url(self): - return reverse('jobs:job_thanks') + return reverse("jobs:job_thanks") def post(self, request, *args, **kwargs): """ @@ -287,14 +281,14 @@ def post(self, request, *args, **kwargs): POST variables and then checked for validity. """ self.object = self.get_object() - if self.request.POST.get('action') == 'review': + if self.request.POST.get("action") == "review": self.object.review() return HttpResponseRedirect(self.get_success_url()) else: return self.get(request) def get_object(self, queryset=None): - """ Show only approved jobs to the public, staff can see all jobs """ + """Show only approved jobs to the public, staff can see all jobs""" job = super().get_object(queryset=queryset) # Only allow creator to preview and only while in draft status if job.creator == self.request.user and job.editable: @@ -309,13 +303,12 @@ def get_object(self, queryset=None): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['user_can_edit'] = ( - self.object.creator == self.request.user or - self.has_jobs_board_admin_access() + context["user_can_edit"] = ( + self.object.creator == self.request.user or self.has_jobs_board_admin_access() ) and self.object.editable - context['under_preview'] = True + context["under_preview"] = True # TODO: why we pass this? - context['form'] = self.get_form(self.form_class) + context["form"] = self.get_form(self.form_class) return context @@ -324,26 +317,21 @@ class JobReviewCommentCreate(LoginRequiredMixin, JobMixin, CreateView): form_class = JobReviewCommentForm def get_success_url(self): - return reverse('jobs:job_detail', kwargs={'pk': self.request.POST.get('job')}) + return reverse("jobs:job_detail", kwargs={"pk": self.request.POST.get("job")}) def form_valid(self, form): - if (self.request.user.username != form.instance.job.creator.username and not - self.has_jobs_board_admin_access()): - return HttpResponse('Unauthorized', status=401) - action = self.request.POST.get('action') - valid_actions = {'approve': Job.STATUS_APPROVED, 'reject': Job.STATUS_REJECTED} + if self.request.user.username != form.instance.job.creator.username and not self.has_jobs_board_admin_access(): + return HttpResponse("Unauthorized", status=401) + action = self.request.POST.get("action") + valid_actions = {"approve": Job.STATUS_APPROVED, "reject": Job.STATUS_REJECTED} if action is not None and action in valid_actions: if not self.has_jobs_board_admin_access(): - return HttpResponse('Unauthorized', status=401) + return HttpResponse("Unauthorized", status=401) action_status = valid_actions.get(action) getattr(form.instance.job, action)(self.request.user) - messages.add_message( - self.request, messages.SUCCESS, - f"'{form.instance.job}' {action_status}." - ) + messages.add_message(self.request, messages.SUCCESS, f"'{form.instance.job}' {action_status}.") else: - messages.add_message(self.request, messages.SUCCESS, - 'Your comment has been posted.') + messages.add_message(self.request, messages.SUCCESS, "Your comment has been posted.") form.instance.creator = self.request.user return super().form_valid(form) @@ -352,25 +340,25 @@ class JobCreate(LoginRequiredMixin, JobMixin, CreateView): model = Job form_class = JobForm - login_message = 'Please login to create a job posting.' + login_message = "Please login to create a job posting." def get_success_url(self): - return reverse('jobs:job_preview', kwargs={'pk': self.object.id}) + return reverse("jobs:job_preview", kwargs={"pk": self.object.id}) def get_form_kwargs(self): kwargs = super().get_form_kwargs() - kwargs['request'] = self.request + kwargs["request"] = self.request return kwargs def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['needs_preview'] = not self.has_jobs_board_admin_access() + context["needs_preview"] = not self.has_jobs_board_admin_access() return context def form_valid(self, form): form.instance.creator = self.request.user form.instance.submitted_by = self.request.user - form.instance.status = 'draft' + form.instance.status = "draft" return super().form_valid(form) @@ -384,22 +372,22 @@ def get_queryset(self): return self.request.user.jobs_job_creator.editable() def form_valid(self, form): - """ set last_modified_by to the current user """ + """set last_modified_by to the current user""" form.instance.last_modified_by = self.request.user return super().form_valid(form) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['form_action'] = 'update' - context['next'] = self.request.GET.get('next') or self.request.POST.get('next') - context['needs_preview'] = not self.has_jobs_board_admin_access() + context["form_action"] = "update" + context["next"] = self.request.GET.get("next") or self.request.POST.get("next") + context["needs_preview"] = not self.has_jobs_board_admin_access() return context def get_success_url(self): - next_url = self.request.POST.get('next') + next_url = self.request.POST.get("next") if next_url: return next_url elif self.object.pk: - return reverse('jobs:job_preview', kwargs={'pk': self.object.id}) + return reverse("jobs:job_preview", kwargs={"pk": self.object.id}) else: return super().get_success_url() diff --git a/mailing/admin.py b/mailing/admin.py index 72f78222b..e9b054f3c 100644 --- a/mailing/admin.py +++ b/mailing/admin.py @@ -13,16 +13,15 @@ class BaseEmailTemplateAdmin(admin.ModelAdmin): readonly_fields = ["created_at", "updated_at"] search_fields = ["internal_name"] fieldsets = ( - (None, { - 'fields': ('internal_name',) - }), - ('Email template', { - 'fields': ('subject', 'content') - }), - ('Timestamps', { - 'classes': ('collapse',), - 'fields': ('created_at', 'updated_at'), - }), + (None, {"fields": ("internal_name",)}), + ("Email template", {"fields": ("subject", "content")}), + ( + "Timestamps", + { + "classes": ("collapse",), + "fields": ("created_at", "updated_at"), + }, + ), ) def get_form(self, *args, **kwargs): diff --git a/mailing/apps.py b/mailing/apps.py index 3021815de..ae006bcb4 100644 --- a/mailing/apps.py +++ b/mailing/apps.py @@ -2,4 +2,4 @@ class MailingConfig(AppConfig): - name = 'mailing' + name = "mailing" diff --git a/mailing/forms.py b/mailing/forms.py index 59f5676e7..c688f43ca 100644 --- a/mailing/forms.py +++ b/mailing/forms.py @@ -5,7 +5,6 @@ class BaseEmailTemplateForm(forms.ModelForm): - def clean_content(self): content = self.cleaned_data["content"] try: diff --git a/mailing/tests/forms.py b/mailing/tests/forms.py index b433adea6..0c2bbbd7c 100644 --- a/mailing/tests/forms.py +++ b/mailing/tests/forms.py @@ -9,5 +9,6 @@ class TestBaseEmailTemplateForm(BaseEmailTemplateForm): class Meta: """Metaclass for the form.""" + model = MockEmailTemplate fields = "__all__" diff --git a/mailing/tests/models.py b/mailing/tests/models.py index 917e8dfb9..04d6de619 100644 --- a/mailing/tests/models.py +++ b/mailing/tests/models.py @@ -8,5 +8,6 @@ class MockEmailTemplate(BaseEmailTemplate): class Meta: """Metaclass for MockEmailTemplate to avoid creating a table in the database.""" - app_label = 'mailing' + + app_label = "mailing" managed = False diff --git a/mailing/tests/test_forms.py b/mailing/tests/test_forms.py index f7a0c6890..1e4c710a1 100644 --- a/mailing/tests/test_forms.py +++ b/mailing/tests/test_forms.py @@ -1,11 +1,11 @@ """Tests for mailing app forms.""" + from django.test import TestCase from mailing.tests.forms import TestBaseEmailTemplateForm class BaseEmailTemplateFormTests(TestCase): - def setUp(self): self.data = { "content": "Hi, {{ name }}\n\nThis is a message to you.", diff --git a/manage.py b/manage.py index 22c8d4c7b..5fbed8edb 100755 --- a/manage.py +++ b/manage.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" + import os import sys @@ -18,5 +19,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/membership/apps.py b/membership/apps.py index 9f3dac7af..413b7967d 100644 --- a/membership/apps.py +++ b/membership/apps.py @@ -2,5 +2,4 @@ class MembershipAppConfig(AppConfig): - - name = 'membership' + name = "membership" diff --git a/membership/tests/test_views.py b/membership/tests/test_views.py index ba1dd8b2e..ad6068844 100644 --- a/membership/tests/test_views.py +++ b/membership/tests/test_views.py @@ -4,14 +4,13 @@ class MembershipViewTests(TestCase): - - @override_flag('psf_membership', active=False) + @override_flag("psf_membership", active=False) def test_membership_landing_ensure_404(self): - response = self.client.get('/membership/') + response = self.client.get("/membership/") self.assertEqual(response.status_code, 404) - @override_flag('psf_membership', active=True) + @override_flag("psf_membership", active=True) def test_membership_landing(self): # Ensure FlagMixin is working - response = self.client.get('/membership/') + response = self.client.get("/membership/") self.assertEqual(response.status_code, 200) diff --git a/membership/urls.py b/membership/urls.py index 8d12b46c9..88a419042 100644 --- a/membership/urls.py +++ b/membership/urls.py @@ -3,5 +3,5 @@ urlpatterns = [ - path('', views.Membership.as_view(), name='membership'), + path("", views.Membership.as_view(), name="membership"), ] diff --git a/membership/views.py b/membership/views.py index 9ed03581d..b05668ebe 100644 --- a/membership/views.py +++ b/membership/views.py @@ -7,6 +7,7 @@ class Membership(FlagMixin, TemplateView): - """ Main membership landing page """ - flag = 'psf_membership' - template_name = 'users/membership.html' + """Main membership landing page""" + + flag = "psf_membership" + template_name = "users/membership.html" diff --git a/minutes/admin.py b/minutes/admin.py index 63f7fdd4d..d9c16b2da 100644 --- a/minutes/admin.py +++ b/minutes/admin.py @@ -6,12 +6,12 @@ @admin.register(Minutes) class MinutesAdmin(ContentManageableModelAdmin): - date_hierarchy = 'date' + date_hierarchy = "date" def get_list_filter(self, request): fields = list(super().get_list_filter(request)) - return fields + ['is_published'] + return fields + ["is_published"] def get_list_display(self, request): fields = list(super().get_list_display(request)) - return fields + ['is_published'] + return fields + ["is_published"] diff --git a/minutes/apps.py b/minutes/apps.py index dfdc40499..817470570 100644 --- a/minutes/apps.py +++ b/minutes/apps.py @@ -2,5 +2,4 @@ class MinutesAppConfig(AppConfig): - - name = 'minutes' + name = "minutes" diff --git a/minutes/feeds.py b/minutes/feeds.py index 3d5d6ec72..b7271144f 100644 --- a/minutes/feeds.py +++ b/minutes/feeds.py @@ -7,15 +7,15 @@ class MinutesFeed(Feed): - title = 'PSF Board Meeting Minutes Feed' - description = 'PSF Board Meeting Minutes' - link = reverse_lazy('minutes_list') + title = "PSF Board Meeting Minutes Feed" + description = "PSF Board Meeting Minutes" + link = reverse_lazy("minutes_list") def items(self): return Minutes.objects.latest()[:20] def item_title(self, item): - return f'PSF Meeting Minutes for {item.date}' + return f"PSF Meeting Minutes for {item.date}" def item_description(self, item): return item.content diff --git a/minutes/management/commands/move_meeting_notes.py b/minutes/management/commands/move_meeting_notes.py index 24c04930e..dda09b28c 100644 --- a/minutes/management/commands/move_meeting_notes.py +++ b/minutes/management/commands/move_meeting_notes.py @@ -8,14 +8,14 @@ class Command(BaseCommand): - """ Move meeting notes from Pages to Minutes app """ + """Move meeting notes from Pages to Minutes app""" def parse_date_from_path(self, path): # Build our date from the URL - path_parts = path.split('/') + path_parts = path.split("/") date = path_parts[-1] - m = re.match(r'^(\d\d\d\d)-(\d\d)-(\d\d)', date) + m = re.match(r"^(\d\d\d\d)-(\d\d)-(\d\d)", date) d = datetime.date( int(m.group(1)), int(m.group(2)), @@ -25,7 +25,7 @@ def parse_date_from_path(self, path): return d def handle(self, *args, **kwargs): - meeting_pages = Page.objects.filter(path__startswith='psf/records/board/minutes/') + meeting_pages = Page.objects.filter(path__startswith="psf/records/board/minutes/") for p in meeting_pages: date = self.parse_date_from_path(p.path) diff --git a/minutes/managers.py b/minutes/managers.py index b25054b45..809701308 100644 --- a/minutes/managers.py +++ b/minutes/managers.py @@ -9,4 +9,4 @@ def published(self): return self.filter(is_published=True) def latest(self): - return self.published().order_by('-date') + return self.published().order_by("-date") diff --git a/minutes/migrations/0001_initial.py b/minutes/migrations/0001_initial.py index 5008d2f09..54609a5ed 100644 --- a/minutes/migrations/0001_initial.py +++ b/minutes/migrations/0001_initial.py @@ -5,29 +5,59 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='Minutes', + name="Minutes", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('date', models.DateField(db_index=True, verbose_name='Meeting Date')), - ('content', markupfield.fields.MarkupField(rendered_field=True)), - ('content_markup_type', models.CharField(max_length=30, choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], default='restructuredtext')), - ('is_published', models.BooleanField(db_index=True, default=False)), - ('_content_rendered', models.TextField(editable=False)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='minutes_minutes_creator', blank=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='minutes_minutes_modified', blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("date", models.DateField(db_index=True, verbose_name="Meeting Date")), + ("content", markupfield.fields.MarkupField(rendered_field=True)), + ( + "content_markup_type", + models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + default="restructuredtext", + ), + ), + ("is_published", models.BooleanField(db_index=True, default=False)), + ("_content_rendered", models.TextField(editable=False)), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="minutes_minutes_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="minutes_minutes_modified", + blank=True, + on_delete=models.CASCADE, + ), + ), ], options={ - 'verbose_name': 'minutes', - 'verbose_name_plural': 'minutes', + "verbose_name": "minutes", + "verbose_name_plural": "minutes", }, bases=(models.Model,), ), diff --git a/minutes/migrations/0002_auto_20150416_1853.py b/minutes/migrations/0002_auto_20150416_1853.py index 1cdde84bf..cadb3ef12 100644 --- a/minutes/migrations/0002_auto_20150416_1853.py +++ b/minutes/migrations/0002_auto_20150416_1853.py @@ -2,16 +2,25 @@ class Migration(migrations.Migration): - dependencies = [ - ('minutes', '0001_initial'), + ("minutes", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='minutes', - name='content_markup_type', - field=models.CharField(max_length=30, default='restructuredtext', choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')]), + model_name="minutes", + name="content_markup_type", + field=models.CharField( + max_length=30, + default="restructuredtext", + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + ), preserve_default=True, ), ] diff --git a/minutes/migrations/0003_alter_minutes_creator_alter_minutes_last_modified_by.py b/minutes/migrations/0003_alter_minutes_creator_alter_minutes_last_modified_by.py index 07e512874..f4dac4f49 100644 --- a/minutes/migrations/0003_alter_minutes_creator_alter_minutes_last_modified_by.py +++ b/minutes/migrations/0003_alter_minutes_creator_alter_minutes_last_modified_by.py @@ -6,21 +6,32 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('minutes', '0002_auto_20150416_1853'), + ("minutes", "0002_auto_20150416_1853"), ] operations = [ migrations.AlterField( - model_name='minutes', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="minutes", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='minutes', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="minutes", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/minutes/models.py b/minutes/models.py index 9101aa5e9..5c74a7cc7 100644 --- a/minutes/models.py +++ b/minutes/models.py @@ -8,29 +8,32 @@ from .managers import MinutesQuerySet -DEFAULT_MARKUP_TYPE = getattr(settings, 'DEFAULT_MARKUP_TYPE', 'restructuredtext') +DEFAULT_MARKUP_TYPE = getattr(settings, "DEFAULT_MARKUP_TYPE", "restructuredtext") class Minutes(ContentManageable): - date = models.DateField(verbose_name='Meeting Date', db_index=True) + date = models.DateField(verbose_name="Meeting Date", db_index=True) content = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE) is_published = models.BooleanField(default=False, db_index=True) objects = MinutesQuerySet.as_manager() class Meta: - verbose_name = 'minutes' - verbose_name_plural = 'minutes' + verbose_name = "minutes" + verbose_name_plural = "minutes" def __str__(self): return "PSF Meeting Minutes %s" % self.date.strftime("%B %d, %Y") def get_absolute_url(self): - return reverse('minutes_detail', kwargs={ - 'year': self.get_date_year(), - 'month': self.get_date_month(), - 'day': self.get_date_day(), - }) + return reverse( + "minutes_detail", + kwargs={ + "year": self.get_date_year(), + "month": self.get_date_month(), + "day": self.get_date_day(), + }, + ) # Helper methods for sitetree def get_date_year(self): diff --git a/minutes/tests/test_models.py b/minutes/tests/test_models.py index 4b5603641..c2fd00c51 100644 --- a/minutes/tests/test_models.py +++ b/minutes/tests/test_models.py @@ -6,27 +6,26 @@ class MinutesModelTests(TestCase): - def setUp(self): self.m1 = Minutes.objects.create( - date=datetime.date(2012, 1, 1), - content='PSF Meeting Minutes #1', - is_published=True + date=datetime.date(2012, 1, 1), content="PSF Meeting Minutes #1", is_published=True ) self.m2 = Minutes.objects.create( - date=datetime.date(2013, 1, 1), - content='PSF Meeting Minutes #2', - is_published=False + date=datetime.date(2013, 1, 1), content="PSF Meeting Minutes #2", is_published=False ) def test_draft(self): - self.assertQuerySetEqual(Minutes.objects.draft(), [''], transform=repr) + self.assertQuerySetEqual( + Minutes.objects.draft(), [""], transform=repr + ) def test_published(self): - self.assertQuerySetEqual(Minutes.objects.published(), [''], transform=repr) + self.assertQuerySetEqual( + Minutes.objects.published(), [""], transform=repr + ) def test_date_methods(self): - self.assertEqual(self.m1.get_date_year(), '2012') - self.assertEqual(self.m1.get_date_month(), '01') - self.assertEqual(self.m1.get_date_day(), '01') + self.assertEqual(self.m1.get_date_year(), "2012") + self.assertEqual(self.m1.get_date_month(), "01") + self.assertEqual(self.m1.get_date_day(), "01") diff --git a/minutes/tests/test_views.py b/minutes/tests/test_views.py index 5bdee65db..0cfbfe2dd 100644 --- a/minutes/tests/test_views.py +++ b/minutes/tests/test_views.py @@ -10,7 +10,6 @@ class MinutesViewsTests(TestCase): - def setUp(self): start_date = datetime.datetime.now() last_month = start_date - datetime.timedelta(weeks=4) @@ -18,70 +17,85 @@ def setUp(self): self.m1 = Minutes.objects.create( date=start_date, - content='Testing', + content="Testing", is_published=False, ) self.m2 = Minutes.objects.create( date=last_month, - content='Testing', + content="Testing", is_published=True, ) self.m3 = Minutes.objects.create( date=two_months, - content='Testing', + content="Testing", is_published=True, ) - self.admin_user = User.objects.create_user('admin', 'admin@admin.com', 'adminpass') + self.admin_user = User.objects.create_user("admin", "admin@admin.com", "adminpass") self.admin_user.is_staff = True self.admin_user.save() def test_list_view(self): - response = self.client.get(reverse('minutes_list')) + response = self.client.get(reverse("minutes_list")) self.assertEqual(response.status_code, 200) - self.assertNotIn(self.m1, response.context['minutes_list']) - self.assertIn(self.m2, response.context['minutes_list']) - self.assertIn(self.m3, response.context['minutes_list']) + self.assertNotIn(self.m1, response.context["minutes_list"]) + self.assertIn(self.m2, response.context["minutes_list"]) + self.assertIn(self.m3, response.context["minutes_list"]) # Test that staff can see drafts - self.client.login(username='admin', password='adminpass') + self.client.login(username="admin", password="adminpass") - response = self.client.get(reverse('minutes_list')) + response = self.client.get(reverse("minutes_list")) self.assertEqual(response.status_code, 200) - self.assertIn(self.m1, response.context['minutes_list']) - self.assertIn(self.m2, response.context['minutes_list']) - self.assertIn(self.m3, response.context['minutes_list']) + self.assertIn(self.m1, response.context["minutes_list"]) + self.assertIn(self.m2, response.context["minutes_list"]) + self.assertIn(self.m3, response.context["minutes_list"]) def test_detail_view(self): - response = self.client.get(reverse('minutes_detail', kwargs={ - 'year': self.m2.date.strftime("%Y"), - 'month': self.m2.date.strftime("%m").zfill(2), - 'day': self.m2.date.strftime("%d").zfill(2), - })) + response = self.client.get( + reverse( + "minutes_detail", + kwargs={ + "year": self.m2.date.strftime("%Y"), + "month": self.m2.date.strftime("%m").zfill(2), + "day": self.m2.date.strftime("%d").zfill(2), + }, + ) + ) self.assertEqual(response.status_code, 200) - self.assertEqual(self.m2, response.context['minutes']) - - response = self.client.get(reverse('minutes_detail', kwargs={ - 'year': self.m1.date.strftime("%Y"), - 'month': self.m1.date.strftime("%m").zfill(2), - 'day': self.m1.date.strftime("%d").zfill(2), - })) + self.assertEqual(self.m2, response.context["minutes"]) + + response = self.client.get( + reverse( + "minutes_detail", + kwargs={ + "year": self.m1.date.strftime("%Y"), + "month": self.m1.date.strftime("%m").zfill(2), + "day": self.m1.date.strftime("%d").zfill(2), + }, + ) + ) self.assertEqual(response.status_code, 404) # Test that staff can see drafts - self.client.login(username='admin', password='adminpass') - - response = self.client.get(reverse('minutes_detail', kwargs={ - 'year': self.m1.date.strftime("%Y"), - 'month': self.m1.date.strftime("%m").zfill(2), - 'day': self.m1.date.strftime("%d").zfill(2), - })) + self.client.login(username="admin", password="adminpass") + + response = self.client.get( + reverse( + "minutes_detail", + kwargs={ + "year": self.m1.date.strftime("%Y"), + "month": self.m1.date.strftime("%m").zfill(2), + "day": self.m1.date.strftime("%d").zfill(2), + }, + ) + ) self.assertEqual(response.status_code, 200) - self.assertEqual(self.m1, response.context['minutes']) + self.assertEqual(self.m1, response.context["minutes"]) diff --git a/minutes/urls.py b/minutes/urls.py index a957df34b..b2d697770 100644 --- a/minutes/urls.py +++ b/minutes/urls.py @@ -4,7 +4,11 @@ urlpatterns = [ - path('', views.MinutesList.as_view(), name='minutes_list'), - path('feed/', MinutesFeed(), name='minutes_feed'), - re_path(r'^(?P[0-9]{4})-(?P[0-9]{2})-(?P[0-9]{2})/$', views.MinutesDetail.as_view(), name='minutes_detail'), + path("", views.MinutesList.as_view(), name="minutes_list"), + path("feed/", MinutesFeed(), name="minutes_feed"), + re_path( + r"^(?P[0-9]{4})-(?P[0-9]{2})-(?P[0-9]{2})/$", + views.MinutesDetail.as_view(), + name="minutes_detail", + ), ] diff --git a/minutes/views.py b/minutes/views.py index 9a88957e4..c729b023e 100644 --- a/minutes/views.py +++ b/minutes/views.py @@ -7,8 +7,8 @@ class MinutesList(ListView): model = Minutes - template_name = 'minutes/minutes_list.html' - context_object_name = 'minutes_list' + template_name = "minutes/minutes_list.html" + context_object_name = "minutes_list" def get_queryset(self): if self.request.user.is_staff: @@ -16,13 +16,13 @@ def get_queryset(self): else: qs = Minutes.objects.published() - return qs.order_by('-date') + return qs.order_by("-date") class MinutesDetail(DetailView): model = Minutes - template_name = 'minutes/minutes_detail.html' - context_object_name = 'minutes' + template_name = "minutes/minutes_detail.html" + context_object_name = "minutes" def get_object(self, queryset=None): # Allow site admins to see drafts @@ -33,9 +33,9 @@ def get_object(self, queryset=None): try: obj = qs.get( - date__year=int(self.kwargs['year']), - date__month=int(self.kwargs['month']), - date__day=int(self.kwargs['day']), + date__year=int(self.kwargs["year"]), + date__month=int(self.kwargs["month"]), + date__day=int(self.kwargs["day"]), ) except ObjectDoesNotExist: raise Http404("Minutes does not exist") @@ -47,8 +47,8 @@ def get_context_data(self, **kwargs): same_year = Minutes.objects.filter( date__year=self.object.date.year, - ).order_by('date') + ).order_by("date") - context['same_year_minutes'] = same_year + context["same_year_minutes"] = same_year return context diff --git a/nominations/admin.py b/nominations/admin.py index 07e516488..565ade48f 100644 --- a/nominations/admin.py +++ b/nominations/admin.py @@ -18,7 +18,7 @@ class NomineeAdmin(admin.ModelAdmin): readonly_fields = ("slug",) def get_ordering(self, request): - return ['election', Lower('user__last_name')] + return ["election", Lower("user__last_name")] @admin.register(Nomination) @@ -28,4 +28,4 @@ class NominationAdmin(admin.ModelAdmin): list_filter = ("election", "accepted", "approved") def get_ordering(self, request): - return ['election', Lower('nominee__user__last_name')] + return ["election", Lower("nominee__user__last_name")] diff --git a/nominations/apps.py b/nominations/apps.py index 1180cd682..320daf75d 100644 --- a/nominations/apps.py +++ b/nominations/apps.py @@ -2,5 +2,4 @@ class NominationsAppConfig(AppConfig): - - name = 'nominations' + name = "nominations" diff --git a/nominations/forms.py b/nominations/forms.py index 4a221fc2f..1172291e0 100644 --- a/nominations/forms.py +++ b/nominations/forms.py @@ -17,9 +17,7 @@ class Meta: "other_affiliations", "nomination_statement", ) - widgets = { - "nomination_statement": MarkupTextarea() - } # , "self_nomination": forms.CheckboxInput()} + widgets = {"nomination_statement": MarkupTextarea()} # , "self_nomination": forms.CheckboxInput()} help_texts = { "name": "Name of the person you are nominating.", "email": "Email address for the person you are nominating.", @@ -56,9 +54,7 @@ def clean_self_nomination(self): class NominationAcceptForm(forms.ModelForm): class Meta: model = Nomination - fields = ( - "accepted", - ) + fields = ("accepted",) help_texts = { "accepted": "If selected, this nomination will be considered accepted and displayed once nominations are public.", } diff --git a/nominations/migrations/0001_initial.py b/nominations/migrations/0001_initial.py index f10416747..c2dd74a16 100644 --- a/nominations/migrations/0001_initial.py +++ b/nominations/migrations/0001_initial.py @@ -7,7 +7,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)] @@ -144,7 +143,5 @@ class Migration(migrations.Migration): to="nominations.Nominee", ), ), - migrations.AlterUniqueTogether( - name="nominee", unique_together={("user", "election")} - ), + migrations.AlterUniqueTogether(name="nominee", unique_together={("user", "election")}), ] diff --git a/nominations/migrations/0002_auto_20190514_1435.py b/nominations/migrations/0002_auto_20190514_1435.py index 336a0ce8f..db9fcf088 100644 --- a/nominations/migrations/0002_auto_20190514_1435.py +++ b/nominations/migrations/0002_auto_20190514_1435.py @@ -5,25 +5,35 @@ class Migration(migrations.Migration): - dependencies = [ - ('nominations', '0001_initial'), + ("nominations", "0001_initial"), ] operations = [ migrations.AddField( - model_name='election', - name='_description_rendered', + model_name="election", + name="_description_rendered", field=models.TextField(editable=False, null=True), ), migrations.AddField( - model_name='election', - name='description', + model_name="election", + name="description", field=markupfield.fields.MarkupField(null=True, rendered_field=True), ), migrations.AddField( - model_name='election', - name='description_markup_type', - field=models.CharField(choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')], default='markdown', editable=False, max_length=30), + model_name="election", + name="description_markup_type", + field=models.CharField( + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + default="markdown", + editable=False, + max_length=30, + ), ), ] diff --git a/nominations/models.py b/nominations/models.py index f52a286be..ab04114db 100644 --- a/nominations/models.py +++ b/nominations/models.py @@ -23,29 +23,21 @@ def __str__(self): date = models.DateField() nominations_open_at = models.DateTimeField(blank=True, null=True) nominations_close_at = models.DateTimeField(blank=True, null=True) - description = MarkupField( - escape_html=False, markup_type="markdown", blank=False, null=True - ) + description = MarkupField(escape_html=False, markup_type="markdown", blank=False, null=True) slug = models.SlugField(max_length=255, blank=True, null=True) @property def nominations_open(self): if self.nominations_open_at and self.nominations_close_at: - return ( - self.nominations_open_at - < datetime.datetime.now(datetime.timezone.utc) - < self.nominations_close_at - ) + return self.nominations_open_at < datetime.datetime.now(datetime.timezone.utc) < self.nominations_close_at return False @property def nominations_complete(self): if self.nominations_close_at: - return self.nominations_close_at < datetime.datetime.now( - datetime.timezone.utc - ) + return self.nominations_close_at < datetime.datetime.now(datetime.timezone.utc) return False @@ -76,9 +68,7 @@ class Meta: def __str__(self): return f"{self.name}" - election = models.ForeignKey( - Election, related_name="nominees", on_delete=models.CASCADE - ) + election = models.ForeignKey(Election, related_name="nominees", on_delete=models.CASCADE) user = models.ForeignKey( User, related_name="nominations_recieved", @@ -104,19 +94,11 @@ def name(self): @property def nominations_received(self): - return ( - self.nominations.filter(accepted=True, approved=True) - .exclude(nominator=self.user) - .all() - ) + return self.nominations.filter(accepted=True, approved=True).exclude(nominator=self.user).all() @property def nominations_pending(self): - return ( - self.nominations.exclude(accepted=False, approved=False) - .exclude(nominator=self.user) - .all() - ) + return self.nominations.exclude(accepted=False, approved=False).exclude(nominator=self.user).all() @property def self_nomination(self): @@ -128,10 +110,7 @@ def display_name(self): @property def display_previous_board_service(self): - if ( - self.self_nomination is not None - and self.self_nomination.previous_board_service - ): + if self.self_nomination is not None and self.self_nomination.previous_board_service: return self.self_nomination.previous_board_service return self.nominations.first().previous_board_service @@ -178,13 +157,9 @@ def __str__(self): previous_board_service = models.CharField(max_length=1024, blank=False, null=True) employer = models.CharField(max_length=1024, blank=False, null=True) other_affiliations = models.CharField(max_length=2048, blank=True, null=True) - nomination_statement = MarkupField( - escape_html=True, markup_type="markdown", blank=False, null=True - ) + nomination_statement = MarkupField(escape_html=True, markup_type="markdown", blank=False, null=True) - nominator = models.ForeignKey( - User, related_name="nominations_made", on_delete=models.CASCADE - ) + nominator = models.ForeignKey(User, related_name="nominations_made", on_delete=models.CASCADE) nominee = models.ForeignKey( Nominee, related_name="nominations", @@ -215,18 +190,10 @@ def get_accept_url(self): ) def editable(self, user=None): - if ( - self.nominee - and user == self.nominee.user - and self.election.nominations_open - ): + if self.nominee and user == self.nominee.user and self.election.nominations_open: return True - if ( - user == self.nominator - and not (self.accepted or self.approved) - and self.election.nominations_open - ): + if user == self.nominator and not (self.accepted or self.approved) and self.election.nominations_open: return True return False @@ -252,7 +219,7 @@ def visible(self, user=None): @receiver(post_save, sender=Nomination) def purge_nomination_pages(sender, instance, created, **kwargs): - """ Purge pages that contain the rendered markup """ + """Purge pages that contain the rendered markup""" # Skip in fixtures if kwargs.get("raw", False): return @@ -266,8 +233,4 @@ def purge_nomination_pages(sender, instance, created, **kwargs): if instance.election: # Purge the election page - purge_url( - reverse( - "nominations:nominees_list", kwargs={"election": instance.election.slug} - ) - ) + purge_url(reverse("nominations:nominees_list", kwargs={"election": instance.election.slug})) diff --git a/nominations/urls.py b/nominations/urls.py index 1815ae2e7..058143ba4 100644 --- a/nominations/urls.py +++ b/nominations/urls.py @@ -3,24 +3,36 @@ app_name = "nominations" urlpatterns = [ - path('elections/', views.ElectionsList.as_view(), name="elections_list"), - path('election//', views.ElectionDetail.as_view(), name="election_detail"), - path('elections//nominees/', views.NomineeList.as_view(), + path("elections/", views.ElectionsList.as_view(), name="elections_list"), + path("election//", views.ElectionDetail.as_view(), name="election_detail"), + path( + "elections//nominees/", + views.NomineeList.as_view(), name="nominees_list", ), - path('elections//nominees//', views.NomineeDetail.as_view(), + path( + "elections//nominees//", + views.NomineeDetail.as_view(), name="nominee_detail", ), - path('/create/', views.NominationCreate.as_view(), + path( + "/create/", + views.NominationCreate.as_view(), name="nomination_create", ), - path('//', views.NominationView.as_view(), + path( + "//", + views.NominationView.as_view(), name="nomination_detail", ), - path('//edit/', views.NominationEdit.as_view(), + path( + "//edit/", + views.NominationEdit.as_view(), name="nomination_edit", ), - path('//accept/', views.NominationAccept.as_view(), + path( + "//accept/", + views.NominationAccept.as_view(), name="nomination_accept", ), ] diff --git a/nominations/views.py b/nominations/views.py index 484f7a7c2..ad6fa8af1 100644 --- a/nominations/views.py +++ b/nominations/views.py @@ -45,9 +45,7 @@ class NomineeList(NominationMixin, ListView): def get_queryset(self, *args, **kwargs): election = Election.objects.get(slug=self.kwargs["election"]) if election.nominations_complete or self.request.user.is_superuser: - return Nominee.objects.filter( - accepted=True, approved=True, election=election - ).exclude(user=None) + return Nominee.objects.filter(accepted=True, approved=True, election=election).exclude(user=None) elif self.request.user.is_authenticated: return Nominee.objects.filter(user=self.request.user) @@ -85,9 +83,7 @@ def get_form_kwargs(self): def get_form_class(self): election = Election.objects.get(slug=self.kwargs["election"]) if election.nominations_complete: - messages.error( - self.request, f"Nominations for {election.name} Election are closed" - ) + messages.error(self.request, f"Nominations for {election.name} Election are closed") raise Http404(f"Nominations for {election.name} Election are closed") return NominationCreateForm @@ -103,9 +99,7 @@ def form_valid(self, form): form.instance.election = Election.objects.get(slug=self.kwargs["election"]) if form.cleaned_data.get("self_nomination", False): try: - nominee = Nominee.objects.get( - user=self.request.user, election=form.instance.election - ) + nominee = Nominee.objects.get(user=self.request.user, election=form.instance.election) except Nominee.DoesNotExist: nominee = Nominee.objects.create( user=self.request.user, @@ -150,7 +144,7 @@ def get_context_data(self, **kwargs): class NominationAccept(LoginRequiredMixin, NominationMixin, UserPassesTestMixin, UpdateView): model = Nomination form_class = NominationAcceptForm - template_name_suffix = '_accept_form' + template_name_suffix = "_accept_form" def test_func(self): return self.request.user == self.get_object().nominee.user diff --git a/pages/admin.py b/pages/admin.py index beb8d3b46..5bf72010c 100644 --- a/pages/admin.py +++ b/pages/admin.py @@ -15,20 +15,21 @@ class DocumentFileInlineAdmin(admin.StackedInline): class PagePathFilter(admin.SimpleListFilter): - """ Admin list filter to allow drilling down by first two levels of pages """ - title = 'Path' - parameter_name = 'pathlimiter' + """Admin list filter to allow drilling down by first two levels of pages""" + + title = "Path" + parameter_name = "pathlimiter" def lookups(self, request, model_admin): - """ Determine the lookups we want to use """ - path_values = Page.objects.order_by('path').values_list('path', flat=True) + """Determine the lookups we want to use""" + path_values = Page.objects.order_by("path").values_list("path", flat=True) path_set = [] for v in path_values: - if v == '': - path_set.append(('', '/')) + if v == "": + path_set.append(("", "/")) else: - parts = v.split('/')[:2] + parts = v.split("/")[:2] new_value = "/".join(parts) new_tuple = (new_value, new_value) if new_tuple not in path_set: @@ -43,12 +44,19 @@ def queryset(self, request, queryset): @admin.register(Page) class PageAdmin(ContentManageableModelAdmin): - search_fields = ['title', 'path'] - list_display = ('get_title', 'path', 'is_published',) - list_filter = [PagePathFilter, 'is_published'] + search_fields = ["title", "path"] + list_display = ( + "get_title", + "path", + "is_published", + ) + list_filter = [PagePathFilter, "is_published"] inlines = [ImageInlineAdmin, DocumentFileInlineAdmin] fieldsets = [ - (None, {'fields': ('title', 'keywords', 'description', 'path', 'content', 'content_markup_type', 'is_published')}), - ('Advanced options', {'classes': ('collapse',), 'fields': ('template_name',)}), + ( + None, + {"fields": ("title", "keywords", "description", "path", "content", "content_markup_type", "is_published")}, + ), + ("Advanced options", {"classes": ("collapse",), "fields": ("template_name",)}), ] save_as = True diff --git a/pages/api.py b/pages/api.py index 073f60e68..5f88777d7 100644 --- a/pages/api.py +++ b/pages/api.py @@ -2,7 +2,9 @@ from pydotorg.resources import GenericResource, OnlyPublishedAuthorization from pydotorg.drf import ( - BaseReadOnlyAPIViewSet, BaseFilterSet, IsStaffOrReadOnly, + BaseReadOnlyAPIViewSet, + BaseFilterSet, + IsStaffOrReadOnly, ) from .models import Page @@ -13,32 +15,35 @@ class PageResource(GenericResource): class Meta(GenericResource.Meta): authorization = OnlyPublishedAuthorization() queryset = Page.objects.all() - resource_name = 'pages/page' + resource_name = "pages/page" fields = [ - 'creator', 'last_modified_by', - 'title', 'keywords', 'description', - 'path', 'content', 'is_published', - 'template_name' - + "creator", + "last_modified_by", + "title", + "keywords", + "description", + "path", + "content", + "is_published", + "template_name", ] filtering = { - 'title': ('exact',), - 'keywords': ('exact', 'icontains'), - 'path': ('exact',), - 'is_published': ('exact',), + "title": ("exact",), + "keywords": ("exact", "icontains"), + "path": ("exact",), + "is_published": ("exact",), } abstract = False class PageFilterSet(BaseFilterSet): - class Meta: model = Page fields = { - 'title': ['exact'], - 'path': ['exact'], - 'keywords': ['exact', 'icontains'], - 'is_published': ['exact'], + "title": ["exact"], + "path": ["exact"], + "keywords": ["exact", "icontains"], + "is_published": ["exact"], } diff --git a/pages/apps.py b/pages/apps.py index da243ee7e..1bc3854d8 100644 --- a/pages/apps.py +++ b/pages/apps.py @@ -2,5 +2,4 @@ class PagesAppConfig(AppConfig): - - name = 'pages' + name = "pages" diff --git a/pages/factories.py b/pages/factories.py index c525f11f4..8a3ea0bae 100644 --- a/pages/factories.py +++ b/pages/factories.py @@ -9,18 +9,17 @@ class PageFactory(DjangoModelFactory): - class Meta: model = Page - django_get_or_create = ('path',) + django_get_or_create = ("path",) - title = factory.Faker('sentence', nb_words=5) + title = factory.Faker("sentence", nb_words=5) path = factory.LazyAttribute(lambda o: slugify(o.title)) - content = factory.Faker('paragraph', nb_sentences=5) + content = factory.Faker("paragraph", nb_sentences=5) creator = factory.SubFactory(UserFactory) def initial_data(): return { - 'pages': PageFactory.create_batch(size=50), + "pages": PageFactory.create_batch(size=50), } diff --git a/pages/management/commands/fix_success_story_images.py b/pages/management/commands/fix_success_story_images.py index 7fc9fe1de..c1c84f60d 100644 --- a/pages/management/commands/fix_success_story_images.py +++ b/pages/management/commands/fix_success_story_images.py @@ -12,10 +12,10 @@ class Command(BaseCommand): - """ Fix success story page images """ + """Fix success story page images""" def get_success_pages(self): - return Page.objects.filter(path__startswith='about/success/') + return Page.objects.filter(path__startswith="about/success/") def image_url(self, path): """ @@ -23,10 +23,10 @@ def image_url(self, path): url for it """ new_url = path.replace(settings.MEDIA_ROOT, settings.MEDIA_URL) - return new_url.replace('//', '/') + return new_url.replace("//", "/") def fix_image(self, path, page): - url = f'http://legacy.python.org{path}' + url = f"http://legacy.python.org{path}" # Retrieve the image r = requests.get(url) @@ -47,11 +47,11 @@ def fix_image(self, path, page): os.makedirs(directory) # Write image data to our location - with open(output_path, 'wb') as f: + with open(output_path, "wb") as f: f.write(r.content) # Re-open the image as a Django File object - reopen = open(output_path, 'rb') + reopen = open(output_path, "rb") new_file = File(reopen) img.image.save(filename, new_file, save=True) @@ -60,14 +60,14 @@ def fix_image(self, path, page): def find_image_paths(self, page): content = page.content.raw - paths = set(re.findall(r'(/files/success.*)\b', content)) + paths = set(re.findall(r"(/files/success.*)\b", content)) if paths: print(f"Found {len(paths)} matches in {page.path}") return paths def process_success_story(self, page): - """ Process an individual success story """ + """Process an individual success story""" image_paths = self.find_image_paths(page) for path in image_paths: diff --git a/pages/management/commands/import_pages_from_svn.py b/pages/management/commands/import_pages_from_svn.py index f6865c0ed..df4709f8b 100644 --- a/pages/management/commands/import_pages_from_svn.py +++ b/pages/management/commands/import_pages_from_svn.py @@ -14,34 +14,34 @@ def fix_image_path(src): - if src.startswith('http'): + if src.startswith("http"): return src - if not src.startswith('/'): - src = '/' + src - url = f'{settings.MEDIA_URL}pages{src}' + if not src.startswith("/"): + src = "/" + src + url = f"{settings.MEDIA_URL}pages{src}" return url class Command(BaseCommand): - """ Import PSF content from svn repository of ReST content """ + """Import PSF content from svn repository of ReST content""" def _build_path(self, filename): - filename = filename.replace(self.SVN_REPO_PATH, '') - filename = filename.replace('/content.ht', '') - filename = filename.replace('/content.rst', '') - filename = filename.replace('/body.html', '') - return filename.strip('/') + filename = filename.replace(self.SVN_REPO_PATH, "") + filename = filename.replace("/content.ht", "") + filename = filename.replace("/content.rst", "") + filename = filename.replace("/body.html", "") + return filename.strip("/") def copy_image(self, content_path, image): - if image.startswith('http'): + if image.startswith("http"): return - if image.startswith('/'): + if image.startswith("/"): image = image[1:] src = os.path.join(os.path.dirname(self.SVN_REPO_PATH), image) else: src = os.path.join(self.SVN_REPO_PATH, content_path, image) - dst = os.path.join(settings.MEDIA_ROOT, 'pages', image) + dst = os.path.join(settings.MEDIA_ROOT, "pages", image) try: os.makedirs(os.path.dirname(dst)) @@ -53,40 +53,37 @@ def copy_image(self, content_path, image): pass def save_images(self, content_path, page): - soup = BeautifulSoup(page.content.rendered, 'lxml') - images = soup.find_all('img') + soup = BeautifulSoup(page.content.rendered, "lxml") + images = soup.find_all("img") for image in images: - self.copy_image(content_path, image.get('src')) - dst = fix_image_path(image.get('src')) - image['src'] = dst - - Image.objects.get_or_create( - page=page, - image=dst - ) - wrapper = BeautifulSoup('
', 'lxml') + self.copy_image(content_path, image.get("src")) + dst = fix_image_path(image.get("src")) + image["src"] = dst + + Image.objects.get_or_create(page=page, image=dst) + wrapper = BeautifulSoup("
", "lxml") [wrapper.div.append(el) for el in soup.body.contents] page.content = "%s" % wrapper.div - page.content_markup_type = 'html' + page.content_markup_type = "html" page.save() def handle(self, *args, **kwargs): - self.SVN_REPO_PATH = getattr(settings, 'PYTHON_ORG_CONTENT_SVN_PATH', None) + self.SVN_REPO_PATH = getattr(settings, "PYTHON_ORG_CONTENT_SVN_PATH", None) if self.SVN_REPO_PATH is None: raise ImproperlyConfigured("PYTHON_ORG_CONTENT_SVN_PATH not defined in settings") matches = [] for root, dirnames, filenames in os.walk(self.SVN_REPO_PATH): for filename in filenames: - if re.match(r'(content\.(ht|rst)|body\.html)$', filename): + if re.match(r"(content\.(ht|rst)|body\.html)$", filename): matches.append(os.path.join(root, filename)) for match in matches: path = self._build_path(match) # Skip homepage - if path == '': + if path == "": continue try: @@ -98,11 +95,11 @@ def handle(self, *args, **kwargs): try: defaults = { - 'title': data['headers'].get('Title', ''), - 'keywords': data['headers'].get('Keywords', ''), - 'description': data['headers'].get('Description', ''), - 'content': data['content'], - 'content_markup_type': data['content_type'], + "title": data["headers"].get("Title", ""), + "keywords": data["headers"].get("Keywords", ""), + "description": data["headers"].get("Description", ""), + "content": data["content"], + "content_markup_type": data["content_type"], } page_obj, _ = Page.objects.get_or_create(path=path, defaults=defaults) diff --git a/pages/middleware.py b/pages/middleware.py index 46b46189a..16dc9c82e 100644 --- a/pages/middleware.py +++ b/pages/middleware.py @@ -5,7 +5,6 @@ class PageFallbackMiddleware: - def __init__(self, get_response): self.get_response = get_response @@ -29,21 +28,20 @@ def __call__(self, request): try: page = qs.get(path=full_path) except Page.DoesNotExist: - has_slash = full_path.endswith('/') - full_path = full_path[:-1] if has_slash else full_path + '/' + has_slash = full_path.endswith("/") + full_path = full_path[:-1] if has_slash else full_path + "/" try: page = qs.get(path=full_path) except Page.DoesNotExist: pass - if (settings.APPEND_SLASH and page is not None and - not request.path.endswith('/')): + if settings.APPEND_SLASH and page is not None and not request.path.endswith("/"): scheme = "https" if request.is_secure() else "http" - new_path = request.path + '/' + new_path = request.path + "/" new_url = f"{scheme}://{request.get_host()}{new_path}" return http.HttpResponsePermanentRedirect(new_url) if page is not None: response = PageView.as_view()(request, path=full_path) - if hasattr(response, 'render'): + if hasattr(response, "render"): response.render() # No page was found. Return the response. diff --git a/pages/migrations/0001_initial.py b/pages/migrations/0001_initial.py index a8eed99ac..74d95cffa 100644 --- a/pages/migrations/0001_initial.py +++ b/pages/migrations/0001_initial.py @@ -8,66 +8,117 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='DocumentFile', + name="DocumentFile", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('document', models.FileField(upload_to='files/', max_length=500)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("document", models.FileField(upload_to="files/", max_length=500)), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='Image', + name="Image", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('image', models.ImageField(upload_to=pages.models.page_image_path, max_length=400)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("image", models.ImageField(upload_to=pages.models.page_image_path, max_length=400)), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='Page', + name="Page", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('title', models.CharField(max_length=500)), - ('keywords', models.CharField(help_text='HTTP meta-keywords', max_length=1000, blank=True)), - ('description', models.TextField(help_text='HTTP meta-description', blank=True)), - ('path', models.CharField(max_length=500, db_index=True, unique=True, validators=[django.core.validators.RegexValidator(message='Please enter a valid URL segment, e.g. "foo" or "foo/bar". Only lowercase letters, numbers, hyphens and periods are allowed.', regex=re.compile('\n ^\n /? # We can optionally start with a /\n ([a-z0-9-\\.]+) # Then at least one path segment...\n (/[a-z0-9-\\.]+)* # And then possibly more "/whatever" segments\n /? # Possibly ending with a slash\n $\n ', 96))])), - ('content', markupfield.fields.MarkupField(rendered_field=True)), - ('content_markup_type', models.CharField(max_length=30, choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], default='restructuredtext')), - ('is_published', models.BooleanField(db_index=True, default=True)), - ('content_type', models.CharField(max_length=150, default='text/html')), - ('_content_rendered', models.TextField(editable=False)), - ('template_name', models.CharField(help_text="Example: 'pages/about.html'. If this isn't provided, the system will use 'pages/default.html'.", max_length=100, blank=True)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='pages_page_creator', blank=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='pages_page_modified', blank=True, on_delete=models.CASCADE)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("title", models.CharField(max_length=500)), + ("keywords", models.CharField(help_text="HTTP meta-keywords", max_length=1000, blank=True)), + ("description", models.TextField(help_text="HTTP meta-description", blank=True)), + ( + "path", + models.CharField( + max_length=500, + db_index=True, + unique=True, + validators=[ + django.core.validators.RegexValidator( + message='Please enter a valid URL segment, e.g. "foo" or "foo/bar". Only lowercase letters, numbers, hyphens and periods are allowed.', + regex=re.compile( + '\n ^\n /? # We can optionally start with a /\n ([a-z0-9-\\.]+) # Then at least one path segment...\n (/[a-z0-9-\\.]+)* # And then possibly more "/whatever" segments\n /? # Possibly ending with a slash\n $\n ', + 96, + ), + ) + ], + ), + ), + ("content", markupfield.fields.MarkupField(rendered_field=True)), + ( + "content_markup_type", + models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + default="restructuredtext", + ), + ), + ("is_published", models.BooleanField(db_index=True, default=True)), + ("content_type", models.CharField(max_length=150, default="text/html")), + ("_content_rendered", models.TextField(editable=False)), + ( + "template_name", + models.CharField( + help_text="Example: 'pages/about.html'. If this isn't provided, the system will use 'pages/default.html'.", + max_length=100, + blank=True, + ), + ), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="pages_page_creator", + blank=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="pages_page_modified", + blank=True, + on_delete=models.CASCADE, + ), + ), ], options={ - 'ordering': ['title', 'path'], + "ordering": ["title", "path"], }, bases=(models.Model,), ), migrations.AddField( - model_name='image', - name='page', - field=models.ForeignKey(to='pages.Page', on_delete=models.CASCADE), + model_name="image", + name="page", + field=models.ForeignKey(to="pages.Page", on_delete=models.CASCADE), preserve_default=True, ), migrations.AddField( - model_name='documentfile', - name='page', - field=models.ForeignKey(to='pages.Page', on_delete=models.CASCADE), + model_name="documentfile", + name="page", + field=models.ForeignKey(to="pages.Page", on_delete=models.CASCADE), preserve_default=True, ), ] diff --git a/pages/migrations/0002_auto_20150416_1853.py b/pages/migrations/0002_auto_20150416_1853.py index bd4550173..1657c8947 100644 --- a/pages/migrations/0002_auto_20150416_1853.py +++ b/pages/migrations/0002_auto_20150416_1853.py @@ -2,16 +2,25 @@ class Migration(migrations.Migration): - dependencies = [ - ('pages', '0001_initial'), + ("pages", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='page', - name='content_markup_type', - field=models.CharField(max_length=30, default='restructuredtext', choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')]), + model_name="page", + name="content_markup_type", + field=models.CharField( + max_length=30, + default="restructuredtext", + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + ), preserve_default=True, ), ] diff --git a/pages/migrations/0003_auto_20230214_2113.py b/pages/migrations/0003_auto_20230214_2113.py index af666269f..499e0f536 100644 --- a/pages/migrations/0003_auto_20230214_2113.py +++ b/pages/migrations/0003_auto_20230214_2113.py @@ -4,15 +4,25 @@ class Migration(migrations.Migration): - dependencies = [ - ('pages', '0002_auto_20150416_1853'), + ("pages", "0002_auto_20150416_1853"), ] operations = [ migrations.AlterField( - model_name='page', - name='content_markup_type', - field=models.CharField(choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text'), ('markdown_unsafe', 'Markdown (unsafe)')], default='restructuredtext', max_length=30), + model_name="page", + name="content_markup_type", + field=models.CharField( + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ("markdown_unsafe", "Markdown (unsafe)"), + ], + default="restructuredtext", + max_length=30, + ), ), ] diff --git a/pages/migrations/0004_alter_page_creator_alter_page_last_modified_by.py b/pages/migrations/0004_alter_page_creator_alter_page_last_modified_by.py index 19c5a6082..72f42398b 100644 --- a/pages/migrations/0004_alter_page_creator_alter_page_last_modified_by.py +++ b/pages/migrations/0004_alter_page_creator_alter_page_last_modified_by.py @@ -6,21 +6,32 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('pages', '0003_auto_20230214_2113'), + ("pages", "0003_auto_20230214_2113"), ] operations = [ migrations.AlterField( - model_name='page', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="page", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='page', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="page", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/pages/models.py b/pages/models.py index 9b67997e1..cf932fc45 100644 --- a/pages/models.py +++ b/pages/models.py @@ -28,9 +28,10 @@ from .managers import PageQuerySet -DEFAULT_MARKUP_TYPE = getattr(settings, 'DEFAULT_MARKUP_TYPE', 'restructuredtext') +DEFAULT_MARKUP_TYPE = getattr(settings, "DEFAULT_MARKUP_TYPE", "restructuredtext") -PAGE_PATH_RE = re.compile(r""" +PAGE_PATH_RE = re.compile( + r""" ^ /? # We can optionally start with a / ([a-z0-9-\.]+) # Then at least one path segment... @@ -38,34 +39,30 @@ /? # Possibly ending with a slash $ """, - re.X + re.X, ) is_valid_page_path = validators.RegexValidator( regex=PAGE_PATH_RE, message=( 'Please enter a valid URL segment, e.g. "foo" or "foo/bar". ' - 'Only lowercase letters, numbers, hyphens and periods are allowed.' + "Only lowercase letters, numbers, hyphens and periods are allowed." ), ) RENDERERS = deepcopy(DEFAULT_MARKUP_TYPES) for i, renderer in enumerate(RENDERERS): - if renderer[0] == 'markdown': + if renderer[0] == "markdown": markdown_index = i -RENDERERS[markdown_index] = ( - 'markdown', - cmarkgfm.github_flavored_markdown_to_html, - 'Markdown' -) +RENDERERS[markdown_index] = ("markdown", cmarkgfm.github_flavored_markdown_to_html, "Markdown") # Add our own Github style Markdown parser, which doesn't apply the default # tagfilter used by Github (we can be more liberal, since we know our page # editors). -def unsafe_markdown_to_html(text, options=0): +def unsafe_markdown_to_html(text, options=0): """Render the given GitHub-flavored Makrdown to HTML. This function is similar to cmarkgfm.github_flavored_markdown_to_html(), @@ -75,15 +72,11 @@ def unsafe_markdown_to_html(text, options=0): """ # Set options for cmarkgfm for "unsafe" renderer, see # https://github.com/theacodes/cmarkgfm#advanced-usage - options = options | ( - cmarkgfmOptions.CMARK_OPT_UNSAFE | - cmarkgfmOptions.CMARK_OPT_GITHUB_PRE_LANG - ) + options = options | (cmarkgfmOptions.CMARK_OPT_UNSAFE | cmarkgfmOptions.CMARK_OPT_GITHUB_PRE_LANG) return cmarkgfm.markdown_to_html_with_extensions( - text, options=options, - extensions=[ - 'table', 'autolink', 'strikethrough', 'tasklist' - ]) + text, options=options, extensions=["table", "autolink", "strikethrough", "tasklist"] + ) + RENDERERS.append( ( @@ -101,27 +94,27 @@ class Page(ContentManageable): path = models.CharField(max_length=500, validators=[is_valid_page_path], unique=True, db_index=True) content = MarkupField(markup_choices=RENDERERS, default_markup_type=DEFAULT_MARKUP_TYPE) is_published = models.BooleanField(default=True, db_index=True) - content_type = models.CharField(max_length=150, default='text/html') + content_type = models.CharField(max_length=150, default="text/html") template_name = models.CharField( max_length=100, blank=True, - help_text="Example: 'pages/about.html'. If this isn't provided, the system will use 'pages/default.html'." + help_text="Example: 'pages/about.html'. If this isn't provided, the system will use 'pages/default.html'.", ) objects = PageQuerySet.as_manager() class Meta: - ordering = ['title', 'path'] + ordering = ["title", "path"] def clean(self): # Strip leading and trailing slashes off self.path. - self.path = self.path.strip('/') + self.path = self.path.strip("/") def get_title(self): if self.title: return self.title else: - return '** No Title **' + return "** No Title **" def __str__(self): return self.title @@ -136,7 +129,7 @@ def purge_fastly_cache(sender, instance, **kwargs): Purge fastly.com cache if in production and the page is published. Requires settings.FASTLY_API_KEY being set """ - purge_url(f'/{instance.path}') + purge_url(f"/{instance.path}") def page_image_path(instance, filename): @@ -144,7 +137,7 @@ def page_image_path(instance, filename): class Image(models.Model): - page = models.ForeignKey('pages.Page', on_delete=models.CASCADE) + page = models.ForeignKey("pages.Page", on_delete=models.CASCADE) image = models.ImageField(upload_to=page_image_path, max_length=400) def __str__(self): @@ -152,9 +145,8 @@ def __str__(self): class DocumentFile(models.Model): - page = models.ForeignKey('pages.Page', on_delete=models.CASCADE) - document = models.FileField(upload_to='files/', max_length=500) + page = models.ForeignKey("pages.Page", on_delete=models.CASCADE) + document = models.FileField(upload_to="files/", max_length=500) def __str__(self): return self.document.url - diff --git a/pages/parser.py b/pages/parser.py index 47a1b4cfb..f856617c3 100644 --- a/pages/parser.py +++ b/pages/parser.py @@ -11,14 +11,14 @@ def read_content_file(dirpath): Copied from old Python.org build process. """ # Read page content - c_ht = os.path.join(dirpath, 'content.ht') - c_rst = os.path.join(dirpath, 'content.rst') + c_ht = os.path.join(dirpath, "content.ht") + c_rst = os.path.join(dirpath, "content.rst") if os.path.exists(c_ht): - raw_input = open(c_ht, 'rb').read() + raw_input = open(c_ht, "rb").read() detection = chardet.detect(raw_input) - input = open(c_ht, encoding=detection['encoding'], errors='ignore') + input = open(c_ht, encoding=detection["encoding"], errors="ignore") msg = email.message_from_file(input) filename = c_ht @@ -38,30 +38,30 @@ def read_content_file(dirpath): def determine_page_content_type(content): - """ Attempt to determine if content is ReST or HTML """ - tags = ['

', '

    ', '

    ', '

    ', '

    ', '
    ', '']
    -    content_type = 'restructuredtext'
    +    """Attempt to determine if content is ReST or HTML"""
    +    tags = ["

    ", "

      ", "

      ", "

      ", "

      ", "
      ", ""]
      +    content_type = "restructuredtext"
           content = content.lower()
       
           for t in tags:
               if t in content:
      -            content_type = 'html'
      +            content_type = "html"
       
           return content_type
       
       
       def parse_page(dirpath):
      -    """ Parse a page given a relative file path """
      +    """Parse a page given a relative file path"""
           filename, msg = read_content_file(dirpath)
       
           content = msg.get_payload()
           content_type = determine_page_content_type(content)
       
           data = {
      -        'headers': dict(msg.items()),
      -        'content': content,
      -        'content_type': content_type,
      -        'filename': filename,
      +        "headers": dict(msg.items()),
      +        "content": content,
      +        "content_type": content_type,
      +        "filename": filename,
           }
       
           return data
      diff --git a/pages/search_indexes.py b/pages/search_indexes.py
      index b41e6f369..9a811581e 100644
      --- a/pages/search_indexes.py
      +++ b/pages/search_indexes.py
      @@ -7,9 +7,9 @@
       
       class PageIndex(indexes.SearchIndex, indexes.Indexable):
           text = indexes.CharField(document=True, use_template=True)
      -    title = indexes.CharField(model_attr='title')
      -    description = indexes.CharField(model_attr='description')
      -    path = indexes.CharField(model_attr='path')
      +    title = indexes.CharField(model_attr="title")
      +    description = indexes.CharField(model_attr="description")
      +    path = indexes.CharField(model_attr="path")
           include_template = indexes.CharField()
       
           def get_model(self):
      @@ -19,12 +19,12 @@ def prepare_include_template(self, obj):
               return "search/includes/pages.page.html"
       
           def prepare_description(self, obj):
      -        """ Create a description if none exists """
      +        """Create a description if none exists"""
               if obj.description:
                   return obj.description
               else:
                   return striptags(truncatewords_html(obj.content.rendered, 50))
       
           def index_queryset(self, using=None):
      -        """ Only index published pages """
      +        """Only index published pages"""
               return self.get_model().objects.filter(is_published=True)
      diff --git a/pages/serializers.py b/pages/serializers.py
      index 7838447f4..3518360e7 100644
      --- a/pages/serializers.py
      +++ b/pages/serializers.py
      @@ -4,16 +4,15 @@
       
       
       class PageSerializer(serializers.HyperlinkedModelSerializer):
      -
           class Meta:
               model = Page
               fields = (
      -            'title',
      -            'path',
      -            'keywords',
      -            'description',
      -            'content',
      -            'is_published',
      -            'template_name',
      -            'resource_uri',
      +            "title",
      +            "path",
      +            "keywords",
      +            "description",
      +            "content",
      +            "is_published",
      +            "template_name",
      +            "resource_uri",
               )
      diff --git a/pages/tests/base.py b/pages/tests/base.py
      index b5046816c..4cebc1351 100644
      --- a/pages/tests/base.py
      +++ b/pages/tests/base.py
      @@ -7,9 +7,9 @@
       
       class BasePageTests(TestCase):
           def setUp(self):
      -        self.p1 = Page.objects.create(title='One', path='one', content='Whatever', is_published=True)
      -        self.p2 = Page.objects.create(title='Two', path='two', content='Yup', is_published=False)
      +        self.p1 = Page.objects.create(title="One", path="one", content="Whatever", is_published=True)
      +        self.p2 = Page.objects.create(title="Two", path="two", content="Yup", is_published=False)
       
      -        self.staff_user = User.objects.create_user(username='staff_user', password='staff_user')
      +        self.staff_user = User.objects.create_user(username="staff_user", password="staff_user")
               self.staff_user.is_staff = True
               self.staff_user.save()
      diff --git a/pages/tests/test_api.py b/pages/tests/test_api.py
      index 1c4cc6184..9688f5013 100644
      --- a/pages/tests/test_api.py
      +++ b/pages/tests/test_api.py
      @@ -7,35 +7,35 @@
       
       
       class PageApiViewsTest(BaseAPITestCase, APITestCase):
      -    app_label = 'pages'
      +    app_label = "pages"
       
           @classmethod
           def setUpTestData(cls):
      -        cls.page = PageFactory(keywords='python, django')
      -        cls.page2 = PageFactory(keywords='django')
      -        cls.page_unpublished = PageFactory(keywords='python', is_published=False)
      +        cls.page = PageFactory(keywords="python, django")
      +        cls.page2 = PageFactory(keywords="django")
      +        cls.page_unpublished = PageFactory(keywords="python", is_published=False)
               cls.staff_user = UserFactory(
      -            username='staffuser',
      -            password='passworduser',
      +            username="staffuser",
      +            password="passworduser",
                   is_staff=True,
               )
      -        cls.Authorization = f'Token {cls.staff_user.auth_token.key}'
      +        cls.Authorization = f"Token {cls.staff_user.auth_token.key}"
       
           def test_get_published_pages(self):
      -        url = self.create_url('page')
      +        url = self.create_url("page")
               response = self.client.get(url)
               self.assertEqual(response.status_code, 200)
               self.assertEqual(len(response.data), 2)
       
           def test_get_all_pages(self):
               # Login to get all pages.
      -        url = self.create_url('page')
      +        url = self.create_url("page")
               response = self.client.get(url, headers={"authorization": self.Authorization})
               self.assertEqual(response.status_code, 200)
               self.assertEqual(len(response.data), 3)
       
           def test_filter_page(self):
      -        url = self.create_url('page', filters={'keywords__icontains': 'PYTHON'})
      +        url = self.create_url("page", filters={"keywords__icontains": "PYTHON"})
               response = self.client.get(url)
               self.assertEqual(response.status_code, 200)
               self.assertEqual(len(response.data), 1)
      @@ -47,7 +47,7 @@ def test_filter_page(self):
       
               # This should return an empty result because normal users
               # cannot see unpublished pages.
      -        url = self.create_url('page', filters={'is_published': False})
      +        url = self.create_url("page", filters={"is_published": False})
               response = self.client.get(url)
               self.assertEqual(response.status_code, 200)
               self.assertEqual(len(response.data), 0)
      @@ -58,25 +58,25 @@ def test_filter_page(self):
               self.assertEqual(len(response.data), 1)
       
           def test_post_page(self):
      -        url = self.create_url('page')
      +        url = self.create_url("page")
               data = {
      -            'title': 'Paradise Lost - The Longest Winter',
      -            'path': '/the-longest-winter/',
      -            'keywords': 'paradise lost, doom death metal',
      -            'is_published': True,
      +            "title": "Paradise Lost - The Longest Winter",
      +            "path": "/the-longest-winter/",
      +            "keywords": "paradise lost, doom death metal",
      +            "is_published": True,
               }
      -        response = self.json_client('POST', url, data)
      +        response = self.json_client("POST", url, data)
               self.assertEqual(response.status_code, 401)
       
               # 'PageViewSet' is read-only.
      -        response = self.json_client('POST', url, data, HTTP_AUTHORIZATION=self.Authorization)
      +        response = self.json_client("POST", url, data, HTTP_AUTHORIZATION=self.Authorization)
               self.assertEqual(response.status_code, 405)
       
           def test_delete_page(self):
      -        url = self.create_url('page', self.page.pk)
      -        response = self.json_client('DELETE', url)
      +        url = self.create_url("page", self.page.pk)
      +        response = self.json_client("DELETE", url)
               self.assertEqual(response.status_code, 401)
       
               # 'PageViewSet' is read-only.
      -        response = self.json_client('DELETE', url, HTTP_AUTHORIZATION=self.Authorization)
      +        response = self.json_client("DELETE", url, HTTP_AUTHORIZATION=self.Authorization)
               self.assertEqual(response.status_code, 405)
      diff --git a/pages/tests/test_models.py b/pages/tests/test_models.py
      index eac62f102..19084c8f4 100644
      --- a/pages/tests/test_models.py
      +++ b/pages/tests/test_models.py
      @@ -9,18 +9,18 @@
       
       class PageModelTests(BasePageTests):
           def test_draft(self):
      -        self.assertQuerySetEqual(Page.objects.draft(), [''], transform=repr)
      +        self.assertQuerySetEqual(Page.objects.draft(), [""], transform=repr)
       
           def test_published(self):
      -        self.assertQuerySetEqual(Page.objects.published(), [''], transform=repr)
      +        self.assertQuerySetEqual(Page.objects.published(), [""], transform=repr)
       
           def test_get_title(self):
      -        one = Page.objects.get(path='one')
      -        self.assertEqual(one.get_title(), 'One')
      +        one = Page.objects.get(path="one")
      +        self.assertEqual(one.get_title(), "One")
       
           def test_get_absolute_url(self):
      -        one = Page.objects.create(title='Testing', path='test/one.html', content='foo')
      -        self.assertEqual('/test/one.html/', one.get_absolute_url())
      +        one = Page.objects.create(title="Testing", path="test/one.html", content="foo")
      +        self.assertEqual("/test/one.html/", one.get_absolute_url())
       
           def test_docutils_security(self):
               # see issue #977 for details
      @@ -35,21 +35,16 @@ def test_docutils_security(self):
       
               fourth line
               """
      -        content_ht = os.path.join(
      -            os.path.dirname(__file__), 'fake_svn_content_checkout', 'content.ht'
      -        )
      +        content_ht = os.path.join(os.path.dirname(__file__), "fake_svn_content_checkout", "content.ht")
               page = Page.objects.create(
      -            title='Testing', content=content.format(content_ht=content_ht),
      -        )
      -        self.assertEqual(
      -            page.content.rendered,
      -            '
      \n

      first line

      \n

      fourth line

      \n
      \n' + title="Testing", + content=content.format(content_ht=content_ht), ) + self.assertEqual(page.content.rendered, "
      \n

      first line

      \n

      fourth line

      \n
      \n") @ddt.ddt class PagePathReTests(unittest.TestCase): - good_paths = ( "path", "path/2", diff --git a/pages/tests/test_parser.py b/pages/tests/test_parser.py index 96b7709bb..b0bc5b1f5 100644 --- a/pages/tests/test_parser.py +++ b/pages/tests/test_parser.py @@ -9,28 +9,24 @@ class PagesParserTests(TestCase): - def test_import_command(self): """ Using a fake reconstruction of the SVN content repo, test our import command """ - fake_svn_path = os.path.join( - os.path.dirname(__file__), - 'fake_svn_content_checkout' - ) + fake_svn_path = os.path.join(os.path.dirname(__file__), "fake_svn_content_checkout") with self.settings(PYTHON_ORG_CONTENT_SVN_PATH=None): with self.assertRaises(ImproperlyConfigured): - call_command('import_pages_from_svn') + call_command("import_pages_from_svn") with self.settings(PYTHON_ORG_CONTENT_SVN_PATH=fake_svn_path): - call_command('import_pages_from_svn') + call_command("import_pages_from_svn") self.assertEqual(Page.objects.count(), 3) - self.assertTrue(Page.objects.get(path='about')) - self.assertTrue(Page.objects.get(path='community')) + self.assertTrue(Page.objects.get(path="about")) + self.assertTrue(Page.objects.get(path="community")) def test_determine_page_content_type(self): test_data = "

      Test

      \n

      Foo bar

      " - self.assertEqual(determine_page_content_type(test_data), 'html') + self.assertEqual(determine_page_content_type(test_data), "html") diff --git a/pages/tests/test_views.py b/pages/tests/test_views.py index d4e17f8ce..e57a9c06a 100644 --- a/pages/tests/test_views.py +++ b/pages/tests/test_views.py @@ -6,33 +6,31 @@ class PageViewTests(BasePageTests): def test_page_view(self): - r = self.client.get('/one/') - self.assertEqual(r.context['page'], self.p1) + r = self.client.get("/one/") + self.assertEqual(r.context["page"], self.p1) # drafts are available only to staff users self.p1.is_published = False self.p1.save() - r = self.client.get('/one/') + r = self.client.get("/one/") self.assertEqual(r.status_code, 404) - self.client.login(username='staff_user', password='staff_user') - r = self.client.get('/one/') + self.client.login(username="staff_user", password="staff_user") + r = self.client.get("/one/") self.assertEqual(r.status_code, 200) def test_with_query_string(self): - r = self.client.get('/one/?foo') - self.assertEqual(r.context['page'], self.p1) + r = self.client.get("/one/?foo") + self.assertEqual(r.context["page"], self.p1) def test_redirect(self): """ Check that redirects still have priority over pages. """ redirect = Redirect.objects.create( - old_path='/%s/' % self.p1.path, - new_path='http://redirected.example.com', - site=Site.objects.get_current() + old_path="/%s/" % self.p1.path, new_path="http://redirected.example.com", site=Site.objects.get_current() ) response = self.client.get(redirect.old_path) self.assertEqual(response.status_code, 301) - self.assertEqual(response['Location'], redirect.new_path) + self.assertEqual(response["Location"], redirect.new_path) redirect.delete() diff --git a/pages/urls.py b/pages/urls.py index df60e2732..d1eb72ab8 100644 --- a/pages/urls.py +++ b/pages/urls.py @@ -2,5 +2,5 @@ from django.urls import path urlpatterns = [ - path('/', PageView.as_view(), name='page_detail'), + path("/", PageView.as_view(), name="page_detail"), ] diff --git a/pages/views.py b/pages/views.py index f7fabe374..d53a87144 100644 --- a/pages/views.py +++ b/pages/views.py @@ -9,16 +9,16 @@ class PageView(DetailView): - template_name = 'pages/default.html' - template_name_field = 'template_name' - context_object_name = 'page' + template_name = "pages/default.html" + template_name_field = "template_name" + context_object_name = "page" # Use "path" as the lookup key, rather than the default "slug". - slug_url_kwarg = 'path' - slug_field = 'path' + slug_url_kwarg = "path" + slug_field = "path" def get_template_names(self): - """ Use the template defined in the model or a default """ + """Use the template defined in the model or a default""" names = [self.template_name] if self.object and self.template_name_field: @@ -40,7 +40,7 @@ def content_type(self): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['in_pages_app'] = True + context["in_pages_app"] = True return context def get(self, request, *args, **kwargs): @@ -48,9 +48,9 @@ def get(self, request, *args, **kwargs): # '/downloads/release/python-XYZ/' if the latter URL doesn't have # 'release_page' (which points to the former URL) field set. # See #956 for details. - matched = re.match(r'/download/releases/([\d.]+)/$', self.request.path) + matched = re.match(r"/download/releases/([\d.]+)/$", self.request.path) if matched is not None: - release_slug = 'python-{}'.format(matched.group(1).replace('.', '')) + release_slug = "python-{}".format(matched.group(1).replace(".", "")) try: Release.objects.get(slug=release_slug, release_page__isnull=True) except Release.DoesNotExist: @@ -58,8 +58,8 @@ def get(self, request, *args, **kwargs): else: return HttpResponsePermanentRedirect( reverse( - 'download:download_release_detail', - kwargs={'release_slug': release_slug}, + "download:download_release_detail", + kwargs={"release_slug": release_slug}, ) ) return super().get(request, *args, **kwargs) diff --git a/peps/apps.py b/peps/apps.py index a59996f2a..037bc1727 100644 --- a/peps/apps.py +++ b/peps/apps.py @@ -2,5 +2,4 @@ class PepsAppConfig(AppConfig): - - name = 'peps' + name = "peps" diff --git a/peps/converters.py b/peps/converters.py index 1d63d7438..b3936639d 100644 --- a/peps/converters.py +++ b/peps/converters.py @@ -12,20 +12,20 @@ from pages.models import Page, Image -PEP_TEMPLATE = 'pages/pep-page.html' -pep_url = lambda num: f'dev/peps/pep-{num}/' +PEP_TEMPLATE = "pages/pep-page.html" +pep_url = lambda num: f"dev/peps/pep-{num}/" def get_peps_last_updated(): - last_update = Page.objects.filter( - path__startswith='dev/peps', - ).aggregate(Max('updated')).get('updated__max') - if last_update is None: - return datetime.datetime( - 1970, 1, 1, tzinfo=datetime.timezone( - datetime.timedelta(0) - ) + last_update = ( + Page.objects.filter( + path__startswith="dev/peps", ) + .aggregate(Max("updated")) + .get("updated__max") + ) + if last_update is None: + return datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone(datetime.timedelta(0))) return last_update @@ -34,12 +34,12 @@ def convert_pep0(artifact_path): Take existing generated pep-0000.html and convert to something suitable for a Python.org Page returns the core body HTML necessary only """ - pep0_path = os.path.join(artifact_path, 'pep-0000.html') + pep0_path = os.path.join(artifact_path, "pep-0000.html") pep0_content = open(pep0_path).read() data = convert_pep_page(0, pep0_content) if data is None: return - return data['content'] + return data["content"] def get_pep0_page(artifact_path, commit=True): @@ -52,11 +52,11 @@ def get_pep0_page(artifact_path, commit=True): pep0_content = convert_pep0(artifact_path) if pep0_content is None: return None, None - pep0_page, _ = Page.objects.get_or_create(path='dev/peps/') - pep0000_page, _ = Page.objects.get_or_create(path='dev/peps/pep-0000/') + pep0_page, _ = Page.objects.get_or_create(path="dev/peps/") + pep0000_page, _ = Page.objects.get_or_create(path="dev/peps/pep-0000/") for page in [pep0_page, pep0000_page]: page.content = pep0_content - page.content_markup_type = 'html' + page.content_markup_type = "html" page.title = "PEP 0 -- Index of Python Enhancement Proposals (PEPs)" page.template_name = PEP_TEMPLATE @@ -67,24 +67,24 @@ def get_pep0_page(artifact_path, commit=True): def fix_headers(soup, data): - """ Remove empty or unwanted headers and find our title """ - header_rows = soup.find_all('th') + """Remove empty or unwanted headers and find our title""" + header_rows = soup.find_all("th") for t in header_rows: - if 'Version:' in t.text: - if t.next_sibling.text == '$Revision$': + if "Version:" in t.text: + if t.next_sibling.text == "$Revision$": t.parent.extract() - if t.next_sibling.text == '': + if t.next_sibling.text == "": t.parent.extract() - if 'Last-Modified:' in t.text: - if '$Date$'in t.next_sibling.text: + if "Last-Modified:" in t.text: + if "$Date$" in t.next_sibling.text: t.parent.extract() - if t.next_sibling.text == '': + if t.next_sibling.text == "": t.parent.extract() - if t.text == 'Title:': - data['title'] = t.next_sibling.text - if t.text == 'Content-Type:': + if t.text == "Title:": + data["title"] = t.next_sibling.text + if t.text == "Content-Type:": t.parent.extract() - if 'Version:' in t.text and 'N/A' in t.next_sibling.text: + if "Version:" in t.text and "N/A" in t.next_sibling.text: t.parent.extract() return soup, data @@ -95,62 +95,59 @@ def convert_pep_page(pep_number, content): Handle different formats that pep2html.py outputs """ data = { - 'title': None, + "title": None, } # Remove leading zeros from PEP number for display purposes - pep_number_humanize = re.sub(r'^0+', '', str(pep_number)) + pep_number_humanize = re.sub(r"^0+", "", str(pep_number)) - if '' in content: - soup = BeautifulSoup(content, 'lxml') - data['title'] = soup.title.text + if "" in content: + soup = BeautifulSoup(content, "lxml") + data["title"] = soup.title.text - if not re.search(r'PEP \d+', data['title']): - data['title'] = 'PEP {} -- {}'.format( + if not re.search(r"PEP \d+", data["title"]): + data["title"] = "PEP {} -- {}".format( pep_number_humanize, soup.title.text, ) - header = soup.body.find('div', class_="header") + header = soup.body.find("div", class_="header") header, data = fix_headers(header, data) - data['header'] = str(header) + data["header"] = str(header) - main_content = soup.body.find('div', class_="content") + main_content = soup.body.find("div", class_="content") - data['main_content'] = str(main_content) - data['content'] = ''.join([ - data['header'], - data['main_content'] - ]) + data["main_content"] = str(main_content) + data["content"] = "".join([data["header"], data["main_content"]]) else: - soup = BeautifulSoup(content, 'lxml') + soup = BeautifulSoup(content, "lxml") soup, data = fix_headers(soup, data) - if not data['title']: - data['title'] = f"PEP {pep_number_humanize} -- " + if not data["title"]: + data["title"] = f"PEP {pep_number_humanize} -- " else: - if not re.search(r'PEP \d+', data['title']): - data['title'] = "PEP {} -- {}".format( + if not re.search(r"PEP \d+", data["title"]): + data["title"] = "PEP {} -- {}".format( pep_number_humanize, - data['title'], + data["title"], ) - data['content'] = str(soup) + data["content"] = str(soup) # Fix PEP links - pep_content = BeautifulSoup(data['content'], 'lxml') + pep_content = BeautifulSoup(data["content"], "lxml") body_links = pep_content.find_all("a") - pep_href_re = re.compile(r'pep-(\d+)\.html') + pep_href_re = re.compile(r"pep-(\d+)\.html") for b in body_links: - m = pep_href_re.search(b.attrs['href']) + m = pep_href_re.search(b.attrs["href"]) # Skip anything not matching 'pep-XXXX.html' if not m: continue - b.attrs['href'] = f'/dev/peps/pep-{m.group(1)}/' + b.attrs["href"] = f"/dev/peps/pep-{m.group(1)}/" # Return early if 'html' or 'body' return None. if pep_content.html is None or pep_content.body is None: @@ -160,7 +157,7 @@ def convert_pep_page(pep_number, content): pep_content.html.unwrap() pep_content.body.unwrap() - data['content'] = str(pep_content) + data["content"] = str(pep_content) return data @@ -169,7 +166,7 @@ def get_pep_page(artifact_path, pep_number, commit=True): Given a pep_number retrieve original PEP source text, rst, or html. Get or create the associated Page and return it """ - pep_path = os.path.join(artifact_path, f'pep-{pep_number}.html') + pep_path = os.path.join(artifact_path, f"pep-{pep_number}.html") if not os.path.exists(pep_path): print(f"PEP Path '{pep_path}' does not exist, skipping") return @@ -178,19 +175,19 @@ def get_pep_page(artifact_path, pep_number, commit=True): if pep_content is None: return None pep_rst_source = os.path.join( - artifact_path, f'pep-{pep_number}.rst', + artifact_path, + f"pep-{pep_number}.rst", ) - pep_ext = '.rst' if os.path.exists(pep_rst_source) else '.txt' - source_link = 'https://github.com/python/peps/blob/master/pep-{}{}'.format( - pep_number, pep_ext) - pep_content['content'] += """Source: {0}""".format(source_link) + pep_ext = ".rst" if os.path.exists(pep_rst_source) else ".txt" + source_link = "https://github.com/python/peps/blob/master/pep-{}{}".format(pep_number, pep_ext) + pep_content["content"] += """Source: {0}""".format(source_link) pep_page, _ = Page.objects.get_or_create(path=pep_url(pep_number)) - pep_page.title = pep_content['title'] + pep_page.title = pep_content["title"] - pep_page.content = pep_content['content'] - pep_page.content_markup_type = 'html' + pep_page.content = pep_content["content"] + pep_page.content_markup_type = "html" pep_page.template_name = PEP_TEMPLATE if commit: @@ -224,16 +221,16 @@ def add_pep_image(artifact_path, pep_number, path): if not FOUND: image = Image(page=page) - with open(image_path, 'rb') as image_obj: + with open(image_path, "rb") as image_obj: image.image.save(path, File(image_obj)) image.save() # Old images used to live alongside html, but now they're in different # places, so update the page accordingly. - soup = BeautifulSoup(page.content.raw, 'lxml') - for img_tag in soup.findAll('img'): - if img_tag['src'] == path: - img_tag['src'] = image.image.url + soup = BeautifulSoup(page.content.raw, "lxml") + for img_tag in soup.findAll("img"): + if img_tag["src"] == path: + img_tag["src"] = image.image.url page.content.raw = str(soup) page.save() @@ -242,7 +239,7 @@ def add_pep_image(artifact_path, pep_number, path): def get_peps_rss(artifact_path): - rss_feed = os.path.join(artifact_path, 'peps.rss') + rss_feed = os.path.join(artifact_path, "peps.rss") if not os.path.exists(rss_feed): return diff --git a/peps/management/commands/dump_pep_pages.py b/peps/management/commands/dump_pep_pages.py index 549b8faa4..f7128e4f8 100644 --- a/peps/management/commands/dump_pep_pages.py +++ b/peps/management/commands/dump_pep_pages.py @@ -8,13 +8,14 @@ class Command(BaseCommand): """ Dump PEP related Pages as indented JSON """ + help = "Dump PEP related Pages as indented JSON" def handle(self, **options): - qs = Page.objects.filter(path__startswith='dev/peps/') + qs = Page.objects.filter(path__startswith="dev/peps/") serializers.serialize( - format='json', + format="json", queryset=qs, indent=4, stream=self.stdout, diff --git a/peps/management/commands/generate_pep_pages.py b/peps/management/commands/generate_pep_pages.py index 9f9010584..76382f1bb 100644 --- a/peps/management/commands/generate_pep_pages.py +++ b/peps/management/commands/generate_pep_pages.py @@ -12,11 +12,9 @@ from dateutil.parser import parse as parsedate -from peps.converters import ( - get_pep0_page, get_pep_page, add_pep_image, get_peps_rss, get_peps_last_updated -) +from peps.converters import get_pep0_page, get_pep_page, add_pep_image, get_peps_rss, get_peps_last_updated -pep_number_re = re.compile(r'pep-(\d+)') +pep_number_re = re.compile(r"pep-(\d+)") class Command(BaseCommand): @@ -31,20 +29,21 @@ class Command(BaseCommand): ./manage.py generate_pep_pages --verbosity=2 """ + help = "Generate PEP Page objects from rendered HTML" def is_pep_page(self, path): - return path.startswith('pep-') and path.endswith('.html') + return path.startswith("pep-") and path.endswith(".html") def is_image(self, path): # All images are pngs - return path.endswith('.png') + return path.endswith(".png") def handle(self, **options): - verbosity = int(options['verbosity']) + verbosity = int(options["verbosity"]) def verbose(msg): - """ Output wrapper """ + """Output wrapper""" if verbosity > 1: print(msg) @@ -60,10 +59,10 @@ def verbose(msg): verbose("== No update to artifacts, we're done here!") return temp_dir = stack.enter_context(TemporaryDirectory()) - tar_ball = stack.enter_context(TarFile.open(fileobj=temp_file, mode='r:gz')) + tar_ball = stack.enter_context(TarFile.open(fileobj=temp_file, mode="r:gz")) tar_ball.extractall(path=temp_dir, numeric_owner=False) - artifacts_path = os.path.join(temp_dir, 'peps') + artifacts_path = os.path.join(temp_dir, "peps") verbose("Generating RSS Feed") peps_rss = get_peps_rss(artifacts_path) @@ -80,7 +79,6 @@ def verbose(msg): # Find pep pages for f in os.listdir(artifacts_path): - if self.is_image(f): verbose(f"- Deferring import of image '{f}'") image_paths.add(f) @@ -91,7 +89,7 @@ def verbose(msg): verbose(f"- Skipping non-PEP file '{f}'") continue - if 'pep-0000.html' in f: + if "pep-0000.html" in f: verbose("- Skipping duplicate PEP0 index") continue @@ -101,11 +99,7 @@ def verbose(msg): pep_number = pep_match.groups(1)[0] p = get_pep_page(artifacts_path, pep_number) if p is None: - verbose( - "- HTML version PEP {!r} cannot be generated.".format( - pep_number - ) - ) + verbose("- HTML version PEP {!r} cannot be generated.".format(pep_number)) verbose(f"====== Title: '{p.title}'") else: verbose(f"- Skipping invalid '{f}'") @@ -115,8 +109,7 @@ def verbose(msg): pep_match = pep_number_re.match(img) if pep_match: pep_number = pep_match.groups(1)[0] - verbose("Generating image for PEP {} at '{}'".format( - pep_number, img)) + verbose("Generating image for PEP {} at '{}'".format(pep_number, img)) add_pep_image(artifacts_path, pep_number, img) else: verbose(f"- Skipping non-PEP related image '{img}'") @@ -125,12 +118,12 @@ def verbose(msg): def get_artifact_tarball(self, stack): artifact_url = settings.PEP_ARTIFACT_URL - if not artifact_url.startswith(('http://', 'https://')): - return stack.enter_context(open(artifact_url, 'rb')) + if not artifact_url.startswith(("http://", "https://")): + return stack.enter_context(open(artifact_url, "rb")) peps_last_updated = get_peps_last_updated() with requests.get(artifact_url, stream=True) as r: - artifact_last_modified = parsedate(r.headers['last-modified']) + artifact_last_modified = parsedate(r.headers["last-modified"]) if peps_last_updated > artifact_last_modified: return diff --git a/peps/templatetags/peps.py b/peps/templatetags/peps.py index 9d90afe24..34da5a628 100644 --- a/peps/templatetags/peps.py +++ b/peps/templatetags/peps.py @@ -7,10 +7,10 @@ @register.simple_tag def get_newest_pep_pages(limit=5): - """ Retrieve the most recently added PEPs """ + """Retrieve the most recently added PEPs""" latest_peps = Page.objects.filter( - path__startswith='dev/peps/', + path__startswith="dev/peps/", is_published=True, - ).order_by('-created')[:limit] + ).order_by("-created")[:limit] return latest_peps diff --git a/peps/tests/__init__.py b/peps/tests/__init__.py index 944cc90aa..ea4df4848 100644 --- a/peps/tests/__init__.py +++ b/peps/tests/__init__.py @@ -2,5 +2,5 @@ from django.conf import settings -FAKE_PEP_REPO = os.path.join(settings.BASE, 'peps/tests/peps/') -FAKE_PEP_ARTIFACT = os.path.join(settings.BASE, 'peps/tests/peps.tar.gz') +FAKE_PEP_REPO = os.path.join(settings.BASE, "peps/tests/peps/") +FAKE_PEP_ARTIFACT = os.path.join(settings.BASE, "peps/tests/peps.tar.gz") diff --git a/peps/tests/test_commands.py b/peps/tests/test_commands.py index 2579a5f99..03c4cbaf5 100644 --- a/peps/tests/test_commands.py +++ b/peps/tests/test_commands.py @@ -14,43 +14,42 @@ from . import FAKE_PEP_ARTIFACT -PEP_ARTIFACT_URL = 'https://example.net/fake-peps.tar.gz' +PEP_ARTIFACT_URL = "https://example.net/fake-peps.tar.gz" @override_settings(PEP_ARTIFACT_URL=PEP_ARTIFACT_URL) class PEPManagementCommandTests(TestCase): - def setUp(self): responses.add( responses.GET, PEP_ARTIFACT_URL, - headers={'Last-Modified': 'Sun, 24 Feb 2019 18:01:42 GMT'}, + headers={"Last-Modified": "Sun, 24 Feb 2019 18:01:42 GMT"}, stream=True, - content_type='application/x-tar', + content_type="application/x-tar", status=200, - body=open(FAKE_PEP_ARTIFACT, 'rb'), + body=open(FAKE_PEP_ARTIFACT, "rb"), ) @responses.activate def test_generate_pep_pages_real_with_remote_artifact(self): - call_command('generate_pep_pages') + call_command("generate_pep_pages") @override_settings(PEP_ARTIFACT_URL=FAKE_PEP_ARTIFACT) def test_generate_pep_pages_real_with_local_artifact(self): - call_command('generate_pep_pages') + call_command("generate_pep_pages") @responses.activate def test_image_generated(self): - call_command('generate_pep_pages') - img = Image.objects.get(page__path='dev/peps/pep-3001/') - soup = BeautifulSoup(img.page.content.raw, 'lxml') - self.assertIn(settings.MEDIA_URL, soup.find('img')['src']) + call_command("generate_pep_pages") + img = Image.objects.get(page__path="dev/peps/pep-3001/") + soup = BeautifulSoup(img.page.content.raw, "lxml") + self.assertIn(settings.MEDIA_URL, soup.find("img")["src"]) @responses.activate def test_dump_pep_pages(self): - call_command('generate_pep_pages') + call_command("generate_pep_pages") stdout = io.StringIO() - call_command('dump_pep_pages', stdout=stdout) + call_command("dump_pep_pages", stdout=stdout) output = stdout.getvalue() - result = list(serializers.deserialize('json', output)) + result = list(serializers.deserialize("json", output)) self.assertGreater(len(result), 0) diff --git a/peps/tests/test_converters.py b/peps/tests/test_converters.py index 833bf7c0e..14ba0f12f 100644 --- a/peps/tests/test_converters.py +++ b/peps/tests/test_converters.py @@ -8,57 +8,47 @@ class PEPConverterTests(TestCase): - def test_source_link(self): - pep = get_pep_page(FAKE_PEP_REPO, '0525') - self.assertEqual(pep.title, 'PEP 525 -- Asynchronous Generators') + pep = get_pep_page(FAKE_PEP_REPO, "0525") + self.assertEqual(pep.title, "PEP 525 -- Asynchronous Generators") self.assertIn( 'Source: https://github.com/python/peps/blob/master/pep-0525.txt', - pep.content.rendered + pep.content.rendered, ) def test_source_link_rst(self): - pep = get_pep_page(FAKE_PEP_REPO, '0012') - self.assertEqual(pep.title, 'PEP 12 -- Sample reStructuredText PEP Template') + pep = get_pep_page(FAKE_PEP_REPO, "0012") + self.assertEqual(pep.title, "PEP 12 -- Sample reStructuredText PEP Template") self.assertIn( 'Source: https://github.com/python/peps/blob/master/pep-0012.rst', - pep.content.rendered + pep.content.rendered, ) def test_invalid_pep_number(self): with captured_stdout() as stdout: - get_pep_page(FAKE_PEP_REPO, '9999999') - self.assertRegex( - stdout.getvalue(), - r"PEP Path '(.*)9999999(.*)' does not exist, skipping" - ) + get_pep_page(FAKE_PEP_REPO, "9999999") + self.assertRegex(stdout.getvalue(), r"PEP Path '(.*)9999999(.*)' does not exist, skipping") def test_add_image_not_found(self): with captured_stdout() as stdout: - add_pep_image(FAKE_PEP_REPO, '0525', '/path/that/does/not/exist') - self.assertRegex( - stdout.getvalue(), - r"Image Path '(.*)/path/that/does/not/exist(.*)' does not exist, skipping" - ) + add_pep_image(FAKE_PEP_REPO, "0525", "/path/that/does/not/exist") + self.assertRegex(stdout.getvalue(), r"Image Path '(.*)/path/that/does/not/exist(.*)' does not exist, skipping") def test_html_do_not_prettify(self): - pep = get_pep_page(FAKE_PEP_REPO, '3001') - self.assertEqual( - pep.title, - 'PEP 3001 -- Procedure for reviewing and improving standard library modules' - ) + pep = get_pep_page(FAKE_PEP_REPO, "3001") + self.assertEqual(pep.title, "PEP 3001 -- Procedure for reviewing and improving standard library modules") self.assertIn( 'Title:' 'Procedure for reviewing and improving ' - 'standard library modules\n', - pep.content.rendered + "standard library modules\n", + pep.content.rendered, ) def test_strip_html_and_body_tags(self): - pep = get_pep_page(FAKE_PEP_REPO, '0525') - self.assertNotIn('', pep.content.rendered) - self.assertNotIn('', pep.content.rendered) - self.assertNotIn('', pep.content.rendered) - self.assertNotIn('', pep.content.rendered) + pep = get_pep_page(FAKE_PEP_REPO, "0525") + self.assertNotIn("", pep.content.rendered) + self.assertNotIn("", pep.content.rendered) + self.assertNotIn("", pep.content.rendered) + self.assertNotIn("", pep.content.rendered) diff --git a/pydotorg/celery.py b/pydotorg/celery.py index 51062cf9b..d7a9188d2 100644 --- a/pydotorg/celery.py +++ b/pydotorg/celery.py @@ -8,8 +8,10 @@ app = Celery("pydotorg") app.config_from_object("django.conf:settings", namespace="CELERY") + @app.task(bind=True) def run_management_command(self, command_name, args, kwargs): management.call_command(command_name, *args, **kwargs) + app.autodiscover_tasks() diff --git a/pydotorg/compilers.py b/pydotorg/compilers.py index 0db08c000..6f026126c 100644 --- a/pydotorg/compilers.py +++ b/pydotorg/compilers.py @@ -2,6 +2,5 @@ class DummySASSCompiler(sass.SASSCompiler): - def compile_file(self, infile, outfile, outdated=False, force=False): pass diff --git a/pydotorg/context_processors.py b/pydotorg/context_processors.py index 461cbcb31..2a75a8722 100644 --- a/pydotorg/context_processors.py +++ b/pydotorg/context_processors.py @@ -3,7 +3,7 @@ def site_info(request): - return {'SITE_INFO': settings.SITE_VARIABLES} + return {"SITE_INFO": settings.SITE_VARIABLES} def url_name(request): @@ -15,18 +15,18 @@ def url_name(request): namespace, url_name_ = match.namespace, match.url_name if namespace: url_name_ = f"{namespace}:{url_name_}" - return {'URL_NAMESPACE': namespace, 'URL_NAME': url_name_} + return {"URL_NAMESPACE": namespace, "URL_NAME": url_name_} def get_host_with_scheme(request): return { - 'GET_HOST_WITH_SCHEME': request.build_absolute_uri('/').rstrip('/'), + "GET_HOST_WITH_SCHEME": request.build_absolute_uri("/").rstrip("/"), } def blog_url(request): return { - 'BLOG_URL': settings.PYTHON_BLOG_URL, + "BLOG_URL": settings.PYTHON_BLOG_URL, } @@ -55,21 +55,16 @@ def user_nav_bar_links(request): {"url": reverse("users:user_nominations_view"), "label": "Nominations"}, ], }, - "sponsorships": { - "label": "Sponsorships Dashboard", - "url": sponsorship_url - } + "sponsorships": {"label": "Sponsorships Dashboard", "url": sponsorship_url}, } if request.user.has_membership: - nav["psf_membership"]['urls'].append({ - "url": reverse("users:user_membership_edit"), - "label": "Edit PSF Basic membership" - }) + nav["psf_membership"]["urls"].append( + {"url": reverse("users:user_membership_edit"), "label": "Edit PSF Basic membership"} + ) else: - nav["psf_membership"]['urls'].append({ - "url": reverse("users:user_membership_create"), - "label": "Become a PSF Basic member" - }) + nav["psf_membership"]["urls"].append( + {"url": reverse("users:user_membership_create"), "label": "Become a PSF Basic member"} + ) return {"USER_NAV_BAR": nav} diff --git a/pydotorg/drf.py b/pydotorg/drf.py index 9b1ccb5ca..acf74fbd0 100644 --- a/pydotorg/drf.py +++ b/pydotorg/drf.py @@ -12,13 +12,8 @@ class IsStaffOrReadOnly(IsAuthenticatedOrReadOnly): - def has_permission(self, request, view): - return ( - request.method in SAFE_METHODS or - request.user and - request.user.is_staff - ) + return request.method in SAFE_METHODS or request.user and request.user.is_staff class BaseAPIViewMixin: @@ -42,13 +37,12 @@ class BaseReadOnlyAPIViewSet(BaseAPIViewMixin, viewsets.ReadOnlyModelViewSet): class BaseFilterSet(filters.FilterSet): - @property def qs(self): errors = [] for param in set(self.data) - set(self.filters): if LOOKUP_SEP not in param: - field, filter = param, 'exact' + field, filter = param, "exact" else: params = param.split(LOOKUP_SEP) if len(params) == 2: @@ -56,11 +50,9 @@ def qs(self): else: *field_parts, filter = params field = LOOKUP_SEP.join(field_parts) - errors.append( - f'{filter!r} is not an allowed filter on the {field!r} field.' - ) + errors.append(f"{filter!r} is not an allowed filter on the {field!r} field.") if errors: - raise serializers.ValidationError({'error': errors}) + raise serializers.ValidationError({"error": errors}) return super().qs @@ -70,28 +62,24 @@ class BaseAPITestCase: DRF's APITestCase implementation in order to run the tests. """ - api_version = 'v2' + api_version = "v2" app_label = None def _check_testcase_config(self): if self.api_version is None: - raise ImproperlyConfigured( - 'Please set \'api_version\' attribute in your test case.' - ) + raise ImproperlyConfigured("Please set 'api_version' attribute in your test case.") if self.app_label is None: - raise ImproperlyConfigured( - 'Please set \'app_label\' attribute in your test case.' - ) + raise ImproperlyConfigured("Please set 'app_label' attribute in your test case.") - def create_url(self, model='', pk=None, *, filters=None, app_label=None): + def create_url(self, model="", pk=None, *, filters=None, app_label=None): self._check_testcase_config() if app_label is None: app_label = self.app_label - base_url = f'/api/{self.api_version}/{app_label}/{model}/' + base_url = f"/api/{self.api_version}/{app_label}/{model}/" if pk is not None: - base_url += '%d/' % pk + base_url += "%d/" % pk if filters is not None: - filters = '?' + urlencode(filters) + filters = "?" + urlencode(filters) return urljoin(base_url, filters) return base_url @@ -100,4 +88,4 @@ def json_client(self, method, url, data=None, **headers): if not data: data = {} client_method = getattr(self.client, method.lower()) - return client_method(url, json.dumps(data), content_type='application/json', **headers) + return client_method(url, json.dumps(data), content_type="application/json", **headers) diff --git a/pydotorg/middleware.py b/pydotorg/middleware.py index 18dad639f..925e30b8b 100644 --- a/pydotorg/middleware.py +++ b/pydotorg/middleware.py @@ -28,8 +28,6 @@ def __call__(self, request): response = self.get_response(request) if hasattr(settings, "GLOBAL_SURROGATE_KEY"): response["Surrogate-Key"] = " ".join( - filter( - None, [settings.GLOBAL_SURROGATE_KEY, response.get("Surrogate-Key")] - ) + filter(None, [settings.GLOBAL_SURROGATE_KEY, response.get("Surrogate-Key")]) ) return response diff --git a/pydotorg/mixins.py b/pydotorg/mixins.py index 261ac198a..0e8cc2736 100644 --- a/pydotorg/mixins.py +++ b/pydotorg/mixins.py @@ -34,8 +34,7 @@ def handle_no_permission(self): self.get_redirect_field_name(), ) if self.raise_exception: - if (self.redirect_unauthenticated_users and not - self.request.user.is_authenticated): + if self.redirect_unauthenticated_users and not self.request.user.is_authenticated: return response raise PermissionDenied(self.get_permission_denied_message()) return response @@ -45,12 +44,10 @@ class GroupRequiredMixin(AccessMixin): group_required = None def get_group_required(self): - if self.group_required is None or ( - not isinstance(self.group_required, (str, list, tuple)) - ): + if self.group_required is None or (not isinstance(self.group_required, (str, list, tuple))): msg = ( '{} requires the "group_required" attribute to be set and be ' - 'one of the following types: string, list or tuple' + "one of the following types: string, list or tuple" ) raise ImproperlyConfigured(msg.format(type(self).__name__)) if not isinstance(self.group_required, (list, tuple)): @@ -62,7 +59,7 @@ def check_membership(self, group): return False if self.request.user.is_superuser: return True - user_groups = self.request.user.groups.values_list('name', flat=True) + user_groups = self.request.user.groups.values_list("name", flat=True) return set(group).intersection(set(user_groups)) def dispatch(self, request, *args, **kwargs): diff --git a/pydotorg/resources.py b/pydotorg/resources.py index 7cc0be981..946023c16 100644 --- a/pydotorg/resources.py +++ b/pydotorg/resources.py @@ -49,7 +49,7 @@ def get_identifier(self, request): return super().get_identifier(request) else: # returns a combination of IP address and hostname. - return "{}_{}".format(request.META.get('REMOTE_ADDR', 'noaddr'), request.META.get('REMOTE_HOST', 'nohost')) + return "{}_{}".format(request.META.get("REMOTE_ADDR", "noaddr"), request.META.get("REMOTE_HOST", "nohost")) def check_active(self, user): return True @@ -59,6 +59,7 @@ class StaffAuthorization(Authorization): """ Everybody can read everything. Staff users can write everything. """ + def read_list(self, object_list, bundle): # Everybody can read return object_list @@ -102,6 +103,7 @@ class OnlyPublishedAuthorization(StaffAuthorization): """ Only staff users can see unpublished objects. """ + def read_list(self, object_list, bundle): if not bundle.request.user.is_staff: return object_list.filter(is_published=True) @@ -119,5 +121,5 @@ class GenericResource(ModelResource): class Meta: authentication = ApiKeyOrGuestAuthentication() authorization = StaffAuthorization() - throttle = CacheThrottle(throttle_at=600) # default is 150 req/hr + throttle = CacheThrottle(throttle_at=600) # default is 150 req/hr abstract = True diff --git a/pydotorg/settings/base.py b/pydotorg/settings/base.py index 9697a6ea8..a6d74b9bd 100644 --- a/pydotorg/settings/base.py +++ b/pydotorg/settings/base.py @@ -6,10 +6,10 @@ ### Basic config -BASE = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) +BASE = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) DEBUG = True SITE_ID = 1 -SECRET_KEY = 'its-a-secret-to-everybody' +SECRET_KEY = "its-a-secret-to-everybody" # Until Sentry works on Py3, do errors the old-fashioned way. ADMINS = [] @@ -17,21 +17,15 @@ # General project information # These are available in the template as SITE_INFO. SITE_VARIABLES = { - 'site_name': 'Python.org', - 'site_descript': 'The official home of the Python Programming Language', + "site_name": "Python.org", + "site_descript": "The official home of the Python Programming Language", } ### Databases -DATABASES = { - 'default': config( - 'DATABASE_URL', - default='postgres:///python.org', - cast=dj_database_url_parser - ) -} +DATABASES = {"default": config("DATABASE_URL", default="postgres:///python.org", cast=dj_database_url_parser)} -DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" """The default primary key field type for Django models. Required during the Django 2.2 -> 4.2 migration. @@ -56,41 +50,41 @@ ### Locale settings -TIME_ZONE = 'UTC' -LANGUAGE_CODE = 'en-us' +TIME_ZONE = "UTC" +LANGUAGE_CODE = "en-us" USE_I18N = True USE_TZ = True -DATE_FORMAT = 'Y-m-d' +DATE_FORMAT = "Y-m-d" ### Files (media and static) -MEDIA_ROOT = os.path.join(BASE, 'media') -MEDIA_URL = '/media/' -MEDIAFILES_LOCATION = 'media' +MEDIA_ROOT = os.path.join(BASE, "media") +MEDIA_URL = "/media/" +MEDIAFILES_LOCATION = "media" # Absolute path to the directory static files should be collected to. # Don't put anything in this directory yourself; store your static files # in apps' "static/" subdirectories and in STATICFILES_DIRS. # Example: "/var/www/example.com/static/" -STATIC_ROOT = os.path.join(BASE, 'static-root') -STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(BASE, "static-root") +STATIC_URL = "/static/" STATICFILES_DIRS = [ - os.path.join(BASE, 'static'), + os.path.join(BASE, "static"), ] STORAGES = { "default": { "BACKEND": "django.core.files.storage.FileSystemStorage", }, "staticfiles": { - "BACKEND": 'pipeline.storage.PipelineStorage', + "BACKEND": "pipeline.storage.PipelineStorage", }, } STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - 'pipeline.finders.PipelineFinder', + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", + "pipeline.finders.PipelineFinder", ) ### Authentication @@ -98,190 +92,177 @@ AUTHENTICATION_BACKENDS = ( # Needed to login by username in Django admin, regardless of `allauth` "django.contrib.auth.backends.ModelBackend", - # `allauth` specific authentication methods, such as login by e-mail "allauth.account.auth_backends.AuthenticationBackend", ) ### Allauth -LOGIN_REDIRECT_URL = 'home' -ACCOUNT_LOGOUT_REDIRECT_URL = 'home' +LOGIN_REDIRECT_URL = "home" +ACCOUNT_LOGOUT_REDIRECT_URL = "home" ACCOUNT_EMAIL_REQUIRED = True ACCOUNT_UNIQUE_EMAIL = True -ACCOUNT_EMAIL_VERIFICATION = 'mandatory' -ACCOUNT_AUTHENTICATION_METHOD = 'username_email' +ACCOUNT_EMAIL_VERIFICATION = "mandatory" +ACCOUNT_AUTHENTICATION_METHOD = "username_email" # TODO: Enable enumeration prevention ACCOUNT_PREVENT_ENUMERATION = False SOCIALACCOUNT_EMAIL_REQUIRED = True SOCIALACCOUNT_EMAIL_VERIFICATION = True SOCIALACCOUNT_QUERY_EMAIL = True -ACCOUNT_USERNAME_VALIDATORS = 'users.validators.username_validators' +ACCOUNT_USERNAME_VALIDATORS = "users.validators.username_validators" ### Templates -TEMPLATES_DIR = os.path.join(BASE, 'templates') +TEMPLATES_DIR = os.path.join(BASE, "templates") TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ TEMPLATES_DIR, ], - 'OPTIONS': { - 'loaders': [ - 'apptemplates.Loader', - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', + "OPTIONS": { + "loaders": [ + "apptemplates.Loader", + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", ], - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.i18n', - 'django.template.context_processors.media', - 'django.template.context_processors.static', - 'django.template.context_processors.tz', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - 'pydotorg.context_processors.site_info', - 'pydotorg.context_processors.url_name', - 'pydotorg.context_processors.get_host_with_scheme', - 'pydotorg.context_processors.blog_url', - 'pydotorg.context_processors.user_nav_bar_links', + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + "pydotorg.context_processors.site_info", + "pydotorg.context_processors.url_name", + "pydotorg.context_processors.get_host_with_scheme", + "pydotorg.context_processors.blog_url", + "pydotorg.context_processors.user_nav_bar_links", ], }, }, ] -FORM_RENDERER = 'django.forms.renderers.DjangoTemplates' +FORM_RENDERER = "django.forms.renderers.DjangoTemplates" ### URLs, WSGI, middleware, etc. -ROOT_URLCONF = 'pydotorg.urls' +ROOT_URLCONF = "pydotorg.urls" # Note that we don't need to activate 'XFrameOptionsMiddleware' and # 'SecurityMiddleware' because we set appropriate headers in python/psf-salt. MIDDLEWARE = [ - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'pydotorg.middleware.AdminNoCaching', - 'pydotorg.middleware.GlobalSurrogateKey', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'waffle.middleware.WaffleMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'pages.middleware.PageFallbackMiddleware', - 'django.contrib.redirects.middleware.RedirectFallbackMiddleware', - 'allauth.account.middleware.AccountMiddleware', + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "pydotorg.middleware.AdminNoCaching", + "pydotorg.middleware.GlobalSurrogateKey", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "waffle.middleware.WaffleMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "pages.middleware.PageFallbackMiddleware", + "django.contrib.redirects.middleware.RedirectFallbackMiddleware", + "allauth.account.middleware.AccountMiddleware", ] -AUTH_USER_MODEL = 'users.User' +AUTH_USER_MODEL = "users.User" -WSGI_APPLICATION = 'pydotorg.wsgi.application' +WSGI_APPLICATION = "pydotorg.wsgi.application" ### Apps INSTALLED_APPS = [ - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.redirects', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.humanize', - - 'admin_interface', - 'colorfield', - 'django.contrib.admin', - 'django.contrib.admindocs', - - 'django_celery_beat', - 'django_translation_aliases', - 'pipeline', - 'sitetree', - 'imagekit', - 'haystack', - 'honeypot', - 'waffle', - 'ordered_model', - 'widget_tweaks', - 'django_countries', - 'sorl.thumbnail', - - 'banners', - 'blogs', - 'boxes', - 'cms', - 'codesamples', - 'community', - 'companies', - 'downloads', - 'events', - 'jobs', - 'mailing', - 'minutes', - 'nominations', - 'pages', - 'peps', - 'sponsors', - 'successstories', - 'users', - 'work_groups', - - 'allauth', - 'allauth.account', - 'allauth.socialaccount', + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.sites", + "django.contrib.redirects", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.humanize", + "admin_interface", + "colorfield", + "django.contrib.admin", + "django.contrib.admindocs", + "django_celery_beat", + "django_translation_aliases", + "pipeline", + "sitetree", + "imagekit", + "haystack", + "honeypot", + "waffle", + "ordered_model", + "widget_tweaks", + "django_countries", + "sorl.thumbnail", + "banners", + "blogs", + "boxes", + "cms", + "codesamples", + "community", + "companies", + "downloads", + "events", + "jobs", + "mailing", + "minutes", + "nominations", + "pages", + "peps", + "sponsors", + "successstories", + "users", + "work_groups", + "allauth", + "allauth.account", + "allauth.socialaccount", #'allauth.socialaccount.providers.facebook', #'allauth.socialaccount.providers.github', #'allauth.socialaccount.providers.openid', #'allauth.socialaccount.providers.twitter', - # Tastypie needs the `users` app to be already loaded. - 'tastypie', - - 'rest_framework', - 'rest_framework.authtoken', - 'django_filters', - 'polymorphic', - 'django_extensions', - 'import_export', + "tastypie", + "rest_framework", + "rest_framework.authtoken", + "django_filters", + "polymorphic", + "django_extensions", + "import_export", ] # Fixtures -FIXTURE_DIRS = ( - os.path.join(BASE, 'fixtures'), -) +FIXTURE_DIRS = (os.path.join(BASE, "fixtures"),) ### Logging LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse' - } - }, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler' + "version": 1, + "disable_existing_loggers": False, + "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}}, + "handlers": { + "mail_admins": { + "level": "ERROR", + "filters": ["require_debug_false"], + "class": "django.utils.log.AdminEmailHandler", } }, - 'loggers': { - 'django.request': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': True, + "loggers": { + "django.request": { + "handlers": ["mail_admins"], + "level": "ERROR", + "propagate": True, }, - } + }, } ### Honeypot -HONEYPOT_FIELD_NAME = 'email_body_text' -HONEYPOT_VALUE = 'write your message' +HONEYPOT_FIELD_NAME = "email_body_text" +HONEYPOT_VALUE = "write your message" ### Blog Feed URL PYTHON_BLOG_FEED_URL = "https://feeds.feedburner.com/PythonInsider" @@ -292,30 +273,26 @@ ### PEP Repo Location PEP_REPO_PATH = None -PEP_ARTIFACT_URL = 'https://pythondotorg-assets-staging.s3.amazonaws.com/fake-peps.tar.gz' +PEP_ARTIFACT_URL = "https://pythondotorg-assets-staging.s3.amazonaws.com/fake-peps.tar.gz" ### Fastly ### FASTLY_API_KEY = False # Set to Fastly API key in production to allow pages to - # be purged on save +# be purged on save # Jobs JOB_THRESHOLD_DAYS = 90 -JOB_FROM_EMAIL = 'jobs@python.org' +JOB_FROM_EMAIL = "jobs@python.org" # Events -EVENTS_TO_EMAIL = 'events@python.org' +EVENTS_TO_EMAIL = "events@python.org" # Sponsors -SPONSORSHIP_NOTIFICATION_FROM_EMAIL = config( - "SPONSORSHIP_NOTIFICATION_FROM_EMAIL", default="sponsors@python.org" -) -SPONSORSHIP_NOTIFICATION_TO_EMAIL = config( - "SPONSORSHIP_NOTIFICATION_TO_EMAIL", default="psf-sponsors@python.org" -) +SPONSORSHIP_NOTIFICATION_FROM_EMAIL = config("SPONSORSHIP_NOTIFICATION_FROM_EMAIL", default="sponsors@python.org") +SPONSORSHIP_NOTIFICATION_TO_EMAIL = config("SPONSORSHIP_NOTIFICATION_TO_EMAIL", default="psf-sponsors@python.org") PYPI_SPONSORS_CSV = os.path.join(BASE, "data", "pypi-sponsors.csv") # Mail -DEFAULT_FROM_EMAIL = 'noreply@python.org' +DEFAULT_FROM_EMAIL = "noreply@python.org" ### Pipeline @@ -324,40 +301,34 @@ ### contrib.messages MESSAGE_TAGS = { - constants.INFO: 'general', + constants.INFO: "general", } ### SecurityMiddleware -X_FRAME_OPTIONS = 'SAMEORIGIN' +X_FRAME_OPTIONS = "SAMEORIGIN" SILENCED_SYSTEM_CHECKS = ["security.W019"] ### django-rest-framework REST_FRAMEWORK = { - 'DEFAULT_AUTHENTICATION_CLASSES': ( - 'rest_framework.authentication.TokenAuthentication', - ), - 'DEFAULT_RENDERER_CLASSES': ( - 'rest_framework.renderers.JSONRenderer', - ), - 'URL_FIELD_NAME': 'resource_uri', - 'DEFAULT_FILTER_BACKENDS': ( - 'django_filters.rest_framework.DjangoFilterBackend', - ), - 'DEFAULT_THROTTLE_CLASSES': ( - 'rest_framework.throttling.AnonRateThrottle', - 'rest_framework.throttling.UserRateThrottle', + "DEFAULT_AUTHENTICATION_CLASSES": ("rest_framework.authentication.TokenAuthentication",), + "DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",), + "URL_FIELD_NAME": "resource_uri", + "DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",), + "DEFAULT_THROTTLE_CLASSES": ( + "rest_framework.throttling.AnonRateThrottle", + "rest_framework.throttling.UserRateThrottle", ), - 'DEFAULT_THROTTLE_RATES': { - 'anon': '100/day', - 'user': '3000/day', + "DEFAULT_THROTTLE_RATES": { + "anon": "100/day", + "user": "3000/day", }, } ### pydotorg.middleware.GlobalSurrogateKey -GLOBAL_SURROGATE_KEY = 'pydotorg-app' +GLOBAL_SURROGATE_KEY = "pydotorg-app" ### PyCon Integration for Sponsor Voucher Codes PYCON_API_KEY = config("PYCON_API_KEY", default="deadbeef-dead-beef-dead-beefdeadbeef") diff --git a/pydotorg/settings/cabotage.py b/pydotorg/settings/cabotage.py index 4661fbf66..263964d87 100644 --- a/pydotorg/settings/cabotage.py +++ b/pydotorg/settings/cabotage.py @@ -9,65 +9,63 @@ DEBUG = TEMPLATE_DEBUG = False DATABASE_CONN_MAX_AGE = 600 -DATABASES['default']['CONN_MAX_AGE'] = DATABASE_CONN_MAX_AGE +DATABASES["default"]["CONN_MAX_AGE"] = DATABASE_CONN_MAX_AGE ## Django Caching CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', - 'LOCATION': 'django_cache_table', + "default": { + "BACKEND": "django.core.cache.backends.db.DatabaseCache", + "LOCATION": "django_cache_table", } } -HAYSTACK_SEARCHBOX_SSL_URL = config( - 'SEARCHBOX_SSL_URL' -) +HAYSTACK_SEARCHBOX_SSL_URL = config("SEARCHBOX_SSL_URL") HAYSTACK_CONNECTIONS = { - 'default': { - 'ENGINE': 'haystack.backends.elasticsearch7_backend.Elasticsearch7SearchEngine', - 'URL': HAYSTACK_SEARCHBOX_SSL_URL, - 'INDEX_NAME': config('HAYSTACK_INDEX', default='haystack-prod'), - 'KWARGS': { - 'ca_certs': '/var/run/secrets/cabotage.io/ca.crt', - } + "default": { + "ENGINE": "haystack.backends.elasticsearch7_backend.Elasticsearch7SearchEngine", + "URL": HAYSTACK_SEARCHBOX_SSL_URL, + "INDEX_NAME": config("HAYSTACK_INDEX", default="haystack-prod"), + "KWARGS": { + "ca_certs": "/var/run/secrets/cabotage.io/ca.crt", + }, }, } -SECRET_KEY = config('SECRET_KEY') +SECRET_KEY = config("SECRET_KEY") -ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv()) +ALLOWED_HOSTS = config("ALLOWED_HOSTS", cast=Csv()) MIDDLEWARE = [ - 'whitenoise.middleware.WhiteNoiseMiddleware', + "whitenoise.middleware.WhiteNoiseMiddleware", ] + MIDDLEWARE -MEDIAFILES_LOCATION = 'media' +MEDIAFILES_LOCATION = "media" STORAGES = { "default": { - "BACKEND": 'custom_storages.storages.MediaStorage', + "BACKEND": "custom_storages.storages.MediaStorage", }, "staticfiles": { - "BACKEND": 'custom_storages.storages.PipelineManifestStorage', + "BACKEND": "custom_storages.storages.PipelineManifestStorage", }, } -EMAIL_HOST = config('EMAIL_HOST') -EMAIL_HOST_USER = config('EMAIL_HOST_USER') -EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD') -EMAIL_PORT = int(config('EMAIL_PORT')) +EMAIL_HOST = config("EMAIL_HOST") +EMAIL_HOST_USER = config("EMAIL_HOST_USER") +EMAIL_HOST_PASSWORD = config("EMAIL_HOST_PASSWORD") +EMAIL_PORT = int(config("EMAIL_PORT")) EMAIL_USE_TLS = True -DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL') +DEFAULT_FROM_EMAIL = config("DEFAULT_FROM_EMAIL") PEP_REPO_PATH = None -PEP_ARTIFACT_URL = config('PEP_ARTIFACT_URL') +PEP_ARTIFACT_URL = config("PEP_ARTIFACT_URL") # Fastly API Key -FASTLY_API_KEY = config('FASTLY_API_KEY') +FASTLY_API_KEY = config("FASTLY_API_KEY") SECURE_SSL_REDIRECT = True -SECURE_PROXY_SSL_HEADER = ('HTTP_FASTLY_SSL', '1') +SECURE_PROXY_SSL_HEADER = ("HTTP_FASTLY_SSL", "1") SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True @@ -76,20 +74,20 @@ ] RAVEN_CONFIG = { - "dsn": config('SENTRY_DSN'), - "release": config('SOURCE_COMMIT'), + "dsn": config("SENTRY_DSN"), + "release": config("SOURCE_COMMIT"), } -AWS_ACCESS_KEY_ID = config('AWS_ACCESS_KEY_ID') -AWS_SECRET_ACCESS_KEY = config('AWS_SECRET_ACCESS_KEY') -AWS_STORAGE_BUCKET_NAME = config('AWS_STORAGE_BUCKET_NAME') -AWS_DEFAULT_ACL = config('AWS_DEFAULT_ACL', default='public-read') +AWS_ACCESS_KEY_ID = config("AWS_ACCESS_KEY_ID") +AWS_SECRET_ACCESS_KEY = config("AWS_SECRET_ACCESS_KEY") +AWS_STORAGE_BUCKET_NAME = config("AWS_STORAGE_BUCKET_NAME") +AWS_DEFAULT_ACL = config("AWS_DEFAULT_ACL", default="public-read") AWS_AUTO_CREATE_BUCKET = False AWS_S3_OBJECT_PARAMETERS = { - 'CacheControl': 'max-age=86400', + "CacheControl": "max-age=86400", } AWS_QUERYSTRING_AUTH = False AWS_S3_FILE_OVERWRITE = False -AWS_S3_REGION_NAME = config('AWS_S3_REGION_NAME', default='us-east-1') +AWS_S3_REGION_NAME = config("AWS_S3_REGION_NAME", default="us-east-1") AWS_S3_USE_SSL = True -AWS_S3_ENDPOINT_URL = config('AWS_S3_ENDPOINT_URL', default='https://s3.amazonaws.com') +AWS_S3_ENDPOINT_URL = config("AWS_S3_ENDPOINT_URL", default="https://s3.amazonaws.com") diff --git a/pydotorg/settings/local.py b/pydotorg/settings/local.py index 6525d9837..5af8c599c 100644 --- a/pydotorg/settings/local.py +++ b/pydotorg/settings/local.py @@ -3,45 +3,36 @@ DEBUG = True -ALLOWED_HOSTS = ['*'] -INTERNAL_IPS = ['127.0.0.1'] +ALLOWED_HOSTS = ["*"] +INTERNAL_IPS = ["127.0.0.1"] # Set the path to the location of the content files for python.org # For example, # PYTHON_ORG_CONTENT_SVN_PATH = '/Users/flavio/working_copies/beta.python.org/build/data' -PYTHON_ORG_CONTENT_SVN_PATH = '' +PYTHON_ORG_CONTENT_SVN_PATH = "" -DATABASES = { - 'default': config( - 'DATABASE_URL', - default='postgres:///pythondotorg', - cast=dj_database_url_parser - ) -} +DATABASES = {"default": config("DATABASE_URL", default="postgres:///pythondotorg", cast=dj_database_url_parser)} -HAYSTACK_SEARCHBOX_SSL_URL = config( - 'SEARCHBOX_SSL_URL', - default='http://127.0.0.1:9200/' -) +HAYSTACK_SEARCHBOX_SSL_URL = config("SEARCHBOX_SSL_URL", default="http://127.0.0.1:9200/") HAYSTACK_CONNECTIONS = { - 'default': { - 'ENGINE': 'haystack.backends.elasticsearch7_backend.Elasticsearch7SearchEngine', - 'URL': HAYSTACK_SEARCHBOX_SSL_URL, - 'INDEX_NAME': 'haystack', + "default": { + "ENGINE": "haystack.backends.elasticsearch7_backend.Elasticsearch7SearchEngine", + "URL": HAYSTACK_SEARCHBOX_SSL_URL, + "INDEX_NAME": "haystack", }, } -EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" # Set the local pep repository path to fetch PEPs from, # or none to fallback to the tarball specified by PEP_ARTIFACT_URL. -PEP_REPO_PATH = config('PEP_REPO_PATH', default=None) # directory path or None +PEP_REPO_PATH = config("PEP_REPO_PATH", default=None) # directory path or None # Set the path to where to fetch PEP artifacts from. # The value can be a local path or a remote URL. # Ignored if PEP_REPO_PATH is set. -PEP_ARTIFACT_URL = os.path.join(BASE, 'peps/tests/peps.tar.gz') +PEP_ARTIFACT_URL = os.path.join(BASE, "peps/tests/peps.tar.gz") # Use Dummy SASS compiler to avoid performance issues and remove the need to # have a sass compiler installed at all during local development if you aren't @@ -55,20 +46,18 @@ # PIPELINE['YUI_BINARY'] = '/usr/bin/java -Xss200048k -jar /usr/share/yui-compressor/yui-compressor.jar' INSTALLED_APPS += [ - 'debug_toolbar', + "debug_toolbar", ] MIDDLEWARE += [ - 'debug_toolbar.middleware.DebugToolbarMiddleware', + "debug_toolbar.middleware.DebugToolbarMiddleware", ] CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'LOCATION': 'pythondotorg-local-cache', + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "pythondotorg-local-cache", } } -REST_FRAMEWORK['DEFAULT_RENDERER_CLASSES'] += ( - 'rest_framework.renderers.BrowsableAPIRenderer', -) +REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] += ("rest_framework.renderers.BrowsableAPIRenderer",) diff --git a/pydotorg/settings/pipeline.py b/pydotorg/settings/pipeline.py index 187613447..02886e6d7 100644 --- a/pydotorg/settings/pipeline.py +++ b/pydotorg/settings/pipeline.py @@ -3,77 +3,63 @@ from .base import BASE PIPELINE_CSS = { - 'style': { - 'source_filenames': ( - 'sass/style.css', - ), - 'output_filename': 'stylesheets/style.css', - 'extra_context': { - 'title': 'default', - 'media': '', + "style": { + "source_filenames": ("sass/style.css",), + "output_filename": "stylesheets/style.css", + "extra_context": { + "title": "default", + "media": "", }, }, - 'mq': { - 'source_filenames': ( - 'sass/mq.css', - ), - 'output_filename': 'stylesheets/mq.css', - 'extra_context': { - 'media': 'not print, braille, embossed, speech, tty', + "mq": { + "source_filenames": ("sass/mq.css",), + "output_filename": "stylesheets/mq.css", + "extra_context": { + "media": "not print, braille, embossed, speech, tty", }, }, - 'no-mq': { - 'source_filenames': ( - 'sass/no-mq.css', - ), - 'output_filename': 'stylesheets/no-mq.css', - 'extra_context': { - 'media': 'screen', + "no-mq": { + "source_filenames": ("sass/no-mq.css",), + "output_filename": "stylesheets/no-mq.css", + "extra_context": { + "media": "screen", }, }, - 'font-awesome': { - 'source_filenames': ( - 'stylesheets/font-awesome.min.css', - ), - 'output_filename': 'stylesheets/no-mq.css', - 'extra_context': { - 'media': 'screen', + "font-awesome": { + "source_filenames": ("stylesheets/font-awesome.min.css",), + "output_filename": "stylesheets/no-mq.css", + "extra_context": { + "media": "screen", }, }, } PIPELINE_JS = { - 'main': { - 'source_filenames': ( - 'js/plugins.js', - 'js/script.js', + "main": { + "source_filenames": ( + "js/plugins.js", + "js/script.js", ), - 'output_filename': 'js/main-min.js', + "output_filename": "js/main-min.js", }, - 'sponsors': { - 'source_filenames': ( - 'js/sponsors/applicationForm.js', - ), - 'output_filename': 'js/sponsors-min.js', + "sponsors": { + "source_filenames": ("js/sponsors/applicationForm.js",), + "output_filename": "js/sponsors-min.js", }, - 'IE8': { - 'source_filenames': ( - 'js/plugins/IE8.js', - ), - 'output_filename': 'js/plugins/IE8-min.js', + "IE8": { + "source_filenames": ("js/plugins/IE8.js",), + "output_filename": "js/plugins/IE8-min.js", }, - 'getComputedStyle': { - 'source_filenames': ( - 'js/plugins/getComputedStyle-min.js', - ), - 'output_filename': 'js/plugins/getComputedStyle-min.js', + "getComputedStyle": { + "source_filenames": ("js/plugins/getComputedStyle-min.js",), + "output_filename": "js/plugins/getComputedStyle-min.js", }, } PIPELINE = { - 'STYLESHEETS': PIPELINE_CSS, - 'JAVASCRIPT': PIPELINE_JS, - 'DISABLE_WRAPPER': True, + "STYLESHEETS": PIPELINE_CSS, + "JAVASCRIPT": PIPELINE_JS, + "DISABLE_WRAPPER": True, # TODO: ruby-sass is not installed on the server since # https://github.com/python/psf-salt/commit/044c38773ced4b8bbe8df2c4266ef3a295102785 # and we pre-compile SASS files and commit them into codebase so we @@ -81,8 +67,8 @@ # 'COMPILERS': ( # 'pipeline.compilers.sass.SASSCompiler', # ), - 'CSS_COMPRESSOR': 'pipeline.compressors.NoopCompressor', - 'JS_COMPRESSOR': 'pipeline.compressors.NoopCompressor', + "CSS_COMPRESSOR": "pipeline.compressors.NoopCompressor", + "JS_COMPRESSOR": "pipeline.compressors.NoopCompressor", # 'SASS_BINARY': 'cd %s && exec /usr/bin/env sass' % os.path.join(BASE, 'static'), # 'SASS_ARGUMENTS': '--quiet --compass --scss -I $(dirname $(dirname $(gem which susy)))/sass' } diff --git a/pydotorg/settings/static.py b/pydotorg/settings/static.py index 5dcbf6f92..59c200dd8 100644 --- a/pydotorg/settings/static.py +++ b/pydotorg/settings/static.py @@ -9,23 +9,23 @@ DEBUG = TEMPLATE_DEBUG = False HAYSTACK_CONNECTIONS = { - 'default': { - 'ENGINE': 'haystack.backends.elasticsearch5_backend.Elasticsearch5SearchEngine', - 'URL': 'http://127.0.0.1:9200', - 'INDEX_NAME': 'haystack-null', + "default": { + "ENGINE": "haystack.backends.elasticsearch5_backend.Elasticsearch5SearchEngine", + "URL": "http://127.0.0.1:9200", + "INDEX_NAME": "haystack-null", }, } MIDDLEWARE = [ - 'whitenoise.middleware.WhiteNoiseMiddleware', + "whitenoise.middleware.WhiteNoiseMiddleware", ] + MIDDLEWARE -MEDIAFILES_LOCATION = 'media' +MEDIAFILES_LOCATION = "media" STORAGES = { "default": { - "BACKEND": 'custom_storages.storages.MediaStorage', + "BACKEND": "custom_storages.storages.MediaStorage", }, "staticfiles": { - "BACKEND": 'custom_storages.storages.PipelineManifestStorage', + "BACKEND": "custom_storages.storages.PipelineManifestStorage", }, } diff --git a/pydotorg/tests/test_context_processors.py b/pydotorg/tests/test_context_processors.py index b1c8f3ed4..f5ff91b79 100644 --- a/pydotorg/tests/test_context_processors.py +++ b/pydotorg/tests/test_context_processors.py @@ -12,34 +12,36 @@ def setUp(self): self.factory = RequestFactory() def test_url_name(self): - request = self.factory.get('/inner/') - self.assertEqual({'URL_NAMESPACE': '', 'URL_NAME': 'inner'}, context_processors.url_name(request)) + request = self.factory.get("/inner/") + self.assertEqual({"URL_NAMESPACE": "", "URL_NAME": "inner"}, context_processors.url_name(request)) - request = self.factory.get('/events/calendars/') - self.assertEqual({'URL_NAMESPACE': 'events', 'URL_NAME': 'events:calendar_list'}, context_processors.url_name(request)) + request = self.factory.get("/events/calendars/") + self.assertEqual( + {"URL_NAMESPACE": "events", "URL_NAME": "events:calendar_list"}, context_processors.url_name(request) + ) - request = self.factory.get('/getit-404/releases/3.3.3/not-an-actual-thing/') + request = self.factory.get("/getit-404/releases/3.3.3/not-an-actual-thing/") self.assertEqual({}, context_processors.url_name(request)) - request = self.factory.get('/getit-404/releases/3.3.3/\r\n/') + request = self.factory.get("/getit-404/releases/3.3.3/\r\n/") self.assertEqual({}, context_processors.url_name(request)) - request = self.factory.get('/nothing/here/') + request = self.factory.get("/nothing/here/") self.assertEqual({}, context_processors.url_name(request)) def test_blog_url(self): - request = self.factory.get('/about/') - self.assertEqual({'BLOG_URL': settings.PYTHON_BLOG_URL}, context_processors.blog_url(request)) + request = self.factory.get("/about/") + self.assertEqual({"BLOG_URL": settings.PYTHON_BLOG_URL}, context_processors.blog_url(request)) def test_user_nav_bar_links_for_non_psf_members(self): - request = self.factory.get('/about/') - request.user = baker.make(settings.AUTH_USER_MODEL, username='foo') + request = self.factory.get("/about/") + request.user = baker.make(settings.AUTH_USER_MODEL, username="foo") expected_nav = { "account": { "label": "Your Account", "urls": [ - {"url": reverse("users:user_detail", args=['foo']), "label": "View profile"}, + {"url": reverse("users:user_detail", args=["foo"]), "label": "View profile"}, {"url": reverse("users:user_profile_edit"), "label": "Edit profile"}, {"url": reverse("account_change_password"), "label": "Change password"}, ], @@ -54,24 +56,21 @@ def test_user_nav_bar_links_for_non_psf_members(self): "sponsorships": { "label": "Sponsorships Dashboard", "url": None, - } + }, } - self.assertEqual( - {"USER_NAV_BAR": expected_nav}, - context_processors.user_nav_bar_links(request) - ) + self.assertEqual({"USER_NAV_BAR": expected_nav}, context_processors.user_nav_bar_links(request)) def test_user_nav_bar_links_for_psf_members(self): - request = self.factory.get('/about/') - request.user = baker.make(settings.AUTH_USER_MODEL, username='foo') - baker.make('users.Membership', creator=request.user) + request = self.factory.get("/about/") + request.user = baker.make(settings.AUTH_USER_MODEL, username="foo") + baker.make("users.Membership", creator=request.user) expected_nav = { "account": { "label": "Your Account", "urls": [ - {"url": reverse("users:user_detail", args=['foo']), "label": "View profile"}, + {"url": reverse("users:user_detail", args=["foo"]), "label": "View profile"}, {"url": reverse("users:user_profile_edit"), "label": "Edit profile"}, {"url": reverse("account_change_password"), "label": "Change password"}, ], @@ -86,31 +85,24 @@ def test_user_nav_bar_links_for_psf_members(self): "sponsorships": { "label": "Sponsorships Dashboard", "url": None, - } + }, } - self.assertEqual( - {"USER_NAV_BAR": expected_nav}, - context_processors.user_nav_bar_links(request) - ) + self.assertEqual({"USER_NAV_BAR": expected_nav}, context_processors.user_nav_bar_links(request)) def test_user_nav_bar_sponsorship_links(self): - request = self.factory.get('/about/') - request.user = baker.make(settings.AUTH_USER_MODEL, username='foo') + request = self.factory.get("/about/") + request.user = baker.make(settings.AUTH_USER_MODEL, username="foo") baker.make("sponsors.Sponsorship", submited_by=request.user, _quantity=2, _fill_optional=True) - expected_section = { - "label": "Sponsorships Dashboard", - "url": reverse("users:user_sponsorships_dashboard") - } + expected_section = {"label": "Sponsorships Dashboard", "url": reverse("users:user_sponsorships_dashboard")} self.assertEqual( - expected_section, - context_processors.user_nav_bar_links(request)['USER_NAV_BAR']['sponsorships'] + expected_section, context_processors.user_nav_bar_links(request)["USER_NAV_BAR"]["sponsorships"] ) def test_user_nav_bar_links_for_anonymous_user(self): - request = self.factory.get('/about/') + request = self.factory.get("/about/") request.user = AnonymousUser() self.assertEqual({"USER_NAV_BAR": {}}, context_processors.user_nav_bar_links(request)) diff --git a/pydotorg/tests/test_middleware.py b/pydotorg/tests/test_middleware.py index d4a8eef86..5e0b62dcc 100644 --- a/pydotorg/tests/test_middleware.py +++ b/pydotorg/tests/test_middleware.py @@ -5,23 +5,20 @@ class MiddlewareTests(TestCase): - def test_admin_caching(self): - """ Ensure admin is not cached """ - response = self.client.get('/admin/') - self.assertTrue(response.has_header('Cache-Control')) - self.assertEqual(response['Cache-Control'], 'private') + """Ensure admin is not cached""" + response = self.client.get("/admin/") + self.assertTrue(response.has_header("Cache-Control")) + self.assertEqual(response["Cache-Control"], "private") def test_redirects(self): """ More of a sanity check just in case some other middleware interferes. """ redirect = Redirect.objects.create( - old_path='/old_path/', - new_path='http://redirected.example.com', - site=Site.objects.get_current() + old_path="/old_path/", new_path="http://redirected.example.com", site=Site.objects.get_current() ) url = redirect.old_path response = self.client.get(url) self.assertEqual(response.status_code, 301) - self.assertEqual(response['Location'], redirect.new_path) + self.assertEqual(response["Location"], redirect.new_path) diff --git a/pydotorg/tests/test_resources.py b/pydotorg/tests/test_resources.py index 58c3af256..bda230666 100644 --- a/pydotorg/tests/test_resources.py +++ b/pydotorg/tests/test_resources.py @@ -3,14 +3,15 @@ from django.http import HttpRequest from pydotorg.resources import ApiKeyOrGuestAuthentication + User = get_user_model() class TestResources(TestCase): def setUp(self): self.staff_user = User.objects.create_user( - username='staffuser', - password='passworduser', + username="staffuser", + password="passworduser", ) self.staff_user.is_staff = True self.staff_user.save() @@ -20,6 +21,6 @@ def test_authentication(self): auth = ApiKeyOrGuestAuthentication() self.assertTrue(auth.is_authenticated(request)) - request.GET['username'] = self.staff_user.email - request.GET['api_key'] = self.staff_user.api_key.key + request.GET["username"] = self.staff_user.email + request.GET["api_key"] = self.staff_user.api_key.key self.assertTrue(auth.is_authenticated(request)) diff --git a/pydotorg/tests/test_views.py b/pydotorg/tests/test_views.py index d6905a41c..ad293c313 100644 --- a/pydotorg/tests/test_views.py +++ b/pydotorg/tests/test_views.py @@ -8,34 +8,30 @@ class ViewsTests(TestCase): - @factory.django.mute_signals(signals.post_save) def test_download_index_without_release(self): - url = reverse('documentation') + url = reverse("documentation") response = self.client.get(url) - latest_python3 = response.context['latest_python3'] + latest_python3 = response.context["latest_python3"] self.assertIsNone(latest_python3) # We included the link because there two instances of the # "Browse Current Documentation" link. - self.assertContains( - response, - '<a href="https://docs.python.org/3/">Browse Current Documentation</a>' - ) - self.assertContains(response, 'What\'s new in Python 3') + self.assertContains(response, '<a href="https://docs.python.org/3/">Browse Current Documentation</a>') + self.assertContains(response, "What's new in Python 3") @factory.django.mute_signals(signals.post_save) def test_download_index(self): release = Release.objects.create( - name='Python 3.6.0', + name="Python 3.6.0", is_latest=True, is_published=True, ) - url = reverse('documentation') + url = reverse("documentation") response = self.client.get(url) - latest_python3 = response.context['latest_python3'] + latest_python3 = response.context["latest_python3"] self.assertIsNotNone(latest_python3) self.assertEqual(latest_python3.name, release.name) self.assertEqual(latest_python3.get_version(), release.get_version()) - self.assertContains(response, 'Browse Python 3.6.0 Documentation') - self.assertContains(response, 'https://docs.python.org/3/whatsnew/3.6.html') - self.assertContains(response, 'What\'s new in Python 3.6') + self.assertContains(response, "Browse Python 3.6.0 Documentation") + self.assertContains(response, "https://docs.python.org/3/whatsnew/3.6.html") + self.assertContains(response, "What's new in Python 3.6") diff --git a/pydotorg/urls.py b/pydotorg/urls.py index f87ab496b..faac6299a 100644 --- a/pydotorg/urls.py +++ b/pydotorg/urls.py @@ -16,62 +16,54 @@ urlpatterns = [ # homepage - path('', views.IndexView.as_view(), name='home'), - re_path(r'^_health/?', views.health, name='health'), - path('authenticated', views.AuthenticatedView.as_view(), name='authenticated'), - re_path(r'^humans.txt$', TemplateView.as_view(template_name='humans.txt', content_type='text/plain')), - re_path(r'^robots.txt$', TemplateView.as_view(template_name='robots.txt', content_type='text/plain')), - path('shell/', TemplateView.as_view(template_name="python/shell.html"), name='shell'), - + path("", views.IndexView.as_view(), name="home"), + re_path(r"^_health/?", views.health, name="health"), + path("authenticated", views.AuthenticatedView.as_view(), name="authenticated"), + re_path(r"^humans.txt$", TemplateView.as_view(template_name="humans.txt", content_type="text/plain")), + re_path(r"^robots.txt$", TemplateView.as_view(template_name="robots.txt", content_type="text/plain")), + path("shell/", TemplateView.as_view(template_name="python/shell.html"), name="shell"), # python section landing pages - path('about/', TemplateView.as_view(template_name="python/about.html"), name='about'), - + path("about/", TemplateView.as_view(template_name="python/about.html"), name="about"), # duplicated downloads to getit to bypass China's firewall. See # https://github.com/python/pythondotorg/issues/427 for more info. - path('getit/', include('downloads.urls', namespace='getit')), - path('downloads/', include('downloads.urls', namespace='download')), - path('doc/', views.DocumentationIndexView.as_view(), name='documentation'), - path('blogs/', include('blogs.urls')), - path('inner/', TemplateView.as_view(template_name="python/inner.html"), name='inner'), - + path("getit/", include("downloads.urls", namespace="getit")), + path("downloads/", include("downloads.urls", namespace="download")), + path("doc/", views.DocumentationIndexView.as_view(), name="documentation"), + path("blogs/", include("blogs.urls")), + path("inner/", TemplateView.as_view(template_name="python/inner.html"), name="inner"), # other section landing pages - path('psf-landing/', TemplateView.as_view(template_name="psf/index.html"), name='psf-landing'), - path('psf/sponsors/', TemplateView.as_view(template_name="psf/sponsors-list.html"), name='psf-sponsors'), - path('docs-landing/', TemplateView.as_view(template_name="docs/index.html"), name='docs-landing'), - path('pypl-landing/', TemplateView.as_view(template_name="pypl/index.html"), name='pypl-landing'), - path('shop-landing/', TemplateView.as_view(template_name="shop/index.html"), name='shop-landing'), - + path("psf-landing/", TemplateView.as_view(template_name="psf/index.html"), name="psf-landing"), + path("psf/sponsors/", TemplateView.as_view(template_name="psf/sponsors-list.html"), name="psf-sponsors"), + path("docs-landing/", TemplateView.as_view(template_name="docs/index.html"), name="docs-landing"), + path("pypl-landing/", TemplateView.as_view(template_name="pypl/index.html"), name="pypl-landing"), + path("shop-landing/", TemplateView.as_view(template_name="shop/index.html"), name="shop-landing"), # Override /accounts/signup/ to add Honeypot. - path('accounts/signup/', HoneypotSignupView.as_view()), + path("accounts/signup/", HoneypotSignupView.as_view()), # Override /accounts/password/change/ to add Honeypot # and change success URL. - path('accounts/password/change/', CustomPasswordChangeView.as_view(), - name='account_change_password'), - path('accounts/', include('allauth.urls')), - path('box/', include('boxes.urls')), - path('community/', include('community.urls', namespace='community')), - path('community/microbit/', TemplateView.as_view(template_name="community/microbit.html"), name='microbit'), - path('events/', include('events.urls', namespace='events')), - path('jobs/', include('jobs.urls', namespace='jobs')), - path('sponsors/', include('sponsors.urls')), - path('success-stories/', include('successstories.urls')), - path('users/', include('users.urls', namespace='users')), - - path('psf/records/board/minutes/', include('minutes.urls')), - path('membership/', include('membership.urls')), - path('search/', include('haystack.urls')), - path('nominations/', include('nominations.urls')), + path("accounts/password/change/", CustomPasswordChangeView.as_view(), name="account_change_password"), + path("accounts/", include("allauth.urls")), + path("box/", include("boxes.urls")), + path("community/", include("community.urls", namespace="community")), + path("community/microbit/", TemplateView.as_view(template_name="community/microbit.html"), name="microbit"), + path("events/", include("events.urls", namespace="events")), + path("jobs/", include("jobs.urls", namespace="jobs")), + path("sponsors/", include("sponsors.urls")), + path("success-stories/", include("successstories.urls")), + path("users/", include("users.urls", namespace="users")), + path("psf/records/board/minutes/", include("minutes.urls")), + path("membership/", include("membership.urls")), + path("search/", include("haystack.urls")), + path("nominations/", include("nominations.urls")), # admin - path('admin/doc/', include('django.contrib.admindocs.urls')), - path('admin/', admin.site.urls), - + path("admin/doc/", include("django.contrib.admindocs.urls")), + path("admin/", admin.site.urls), # api - path('api/', include(urls_api.v1_api.urls)), - path('api/v2/', include(urls_api.router.urls)), - path('api/v2/', include(urls_api)), - + path("api/", include(urls_api.v1_api.urls)), + path("api/v2/", include(urls_api.router.urls)), + path("api/v2/", include(urls_api)), # storage migration - re_path(r'^m/(?P<url>.*)/$', views.MediaMigrationView.as_view(prefix='media'), name='media_migration_view'), + re_path(r"^m/(?P<url>.*)/$", views.MediaMigrationView.as_view(prefix="media"), name="media_migration_view"), ] urlpatterns += staticfiles_urlpatterns() @@ -79,6 +71,7 @@ if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) import debug_toolbar + urlpatterns = [ - path('__debug__/', include(debug_toolbar.urls)), + path("__debug__/", include(debug_toolbar.urls)), ] + urlpatterns diff --git a/pydotorg/urls_api.py b/pydotorg/urls_api.py index 4afc7122e..5ea39b3ee 100644 --- a/pydotorg/urls_api.py +++ b/pydotorg/urls_api.py @@ -9,19 +9,19 @@ from pages.api import PageViewSet from sponsors.api import LogoPlacementeAPIList, SponsorshipAssetsAPIList -v1_api = Api(api_name='v1') +v1_api = Api(api_name="v1") v1_api.register(PageResource()) v1_api.register(OSResource()) v1_api.register(ReleaseResource()) v1_api.register(ReleaseFileResource()) router = routers.DefaultRouter() -router.register(r'pages/page', PageViewSet, basename='page') -router.register(r'downloads/os', OSViewSet) -router.register(r'downloads/release', ReleaseViewSet, basename='release') -router.register(r'downloads/release_file', ReleaseFileViewSet) +router.register(r"pages/page", PageViewSet, basename="page") +router.register(r"downloads/os", OSViewSet) +router.register(r"downloads/release", ReleaseViewSet, basename="release") +router.register(r"downloads/release_file", ReleaseFileViewSet) urlpatterns = [ - re_path(r'sponsors/logo-placement/', LogoPlacementeAPIList.as_view(), name="logo_placement_list"), - re_path(r'sponsors/sponsorship-assets/', SponsorshipAssetsAPIList.as_view(), name="assets_list"), + re_path(r"sponsors/logo-placement/", LogoPlacementeAPIList.as_view(), name="logo_placement_list"), + re_path(r"sponsors/sponsorship-assets/", SponsorshipAssetsAPIList.as_view(), name="assets_list"), ] diff --git a/pydotorg/views.py b/pydotorg/views.py index 9777cf1aa..c85977de5 100644 --- a/pydotorg/views.py +++ b/pydotorg/views.py @@ -7,7 +7,7 @@ def health(request): - return HttpResponse('OK') + return HttpResponse("OK") class IndexView(TemplateView): @@ -16,9 +16,11 @@ class IndexView(TemplateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context.update({ - 'code_samples': CodeSample.objects.published()[:5], - }) + context.update( + { + "code_samples": CodeSample.objects.published()[:5], + } + ) return context @@ -27,14 +29,16 @@ class AuthenticatedView(TemplateView): class DocumentationIndexView(TemplateView): - template_name = 'python/documentation.html' + template_name = "python/documentation.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context.update({ - 'latest_python2': Release.objects.latest_python2(), - 'latest_python3': Release.objects.latest_python3(), - }) + context.update( + { + "latest_python2": Release.objects.latest_python2(), + "latest_python3": Release.objects.latest_python3(), + } + ) return context @@ -44,11 +48,13 @@ class MediaMigrationView(RedirectView): query_string = False def get_redirect_url(self, *args, **kwargs): - image_path = kwargs['url'] + image_path = kwargs["url"] if self.prefix: - image_path = '/'.join([self.prefix, image_path]) - return '/'.join([ - settings.AWS_S3_ENDPOINT_URL, - settings.AWS_STORAGE_BUCKET_NAME, - image_path, - ]) + image_path = "/".join([self.prefix, image_path]) + return "/".join( + [ + settings.AWS_S3_ENDPOINT_URL, + settings.AWS_STORAGE_BUCKET_NAME, + image_path, + ] + ) diff --git a/pydotorg/wsgi.py b/pydotorg/wsgi.py index 92031712f..cee646812 100644 --- a/pydotorg/wsgi.py +++ b/pydotorg/wsgi.py @@ -13,6 +13,7 @@ framework. """ + import os # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks @@ -25,6 +26,7 @@ # file. This includes Django's development server, if the WSGI_APPLICATION # setting points here. from django.core.wsgi import get_wsgi_application + application = get_wsgi_application() # Apply WSGI middleware here. diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..207ab8054 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,24 @@ +line-length = 120 +src = ["banners", "blogs", "boxes", "cms", "codesamples", "community", "companies", "custom_storages", + "downloads", "events", "fastly", "jobs", "mailing", "membersip", "minutes", "nominations", "pages", + "peps", "pydotorg", "sponsors", "successstories", "users", "work_groups"] +target-version = "py312" + +[lint] +select = ["ALL"] +ignore = ["ANN101", "ANN102", "ANN401", "PLR0913", "RUF012", "COM812", "ISC001", "ERA001", "TD", "FIX002"] + +[format] +quote-style = "double" +indent-style = "space" + +[lint.pydocstyle] +convention = "google" + +[lint.mccabe] +max-complexity = 12 + +[lint.isort] +known-first-party = ["pybama_org", "tests", "config"] + +[lint.per-file-ignores] diff --git a/sponsors/admin.py b/sponsors/admin.py index dc7278c08..965cf0640 100644 --- a/sponsors/admin.py +++ b/sponsors/admin.py @@ -2,8 +2,12 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.sites.models import Site from ordered_model.admin import OrderedModelAdmin -from polymorphic.admin import PolymorphicInlineSupportMixin, StackedPolymorphicInline, PolymorphicParentModelAdmin, \ - PolymorphicChildModelAdmin +from polymorphic.admin import ( + PolymorphicInlineSupportMixin, + StackedPolymorphicInline, + PolymorphicParentModelAdmin, + PolymorphicChildModelAdmin, +) from django.db.models import Subquery from django.template import Context, Template @@ -22,8 +26,13 @@ from sponsors.models import * from sponsors.models.benefits import RequiredAssetMixin from sponsors import views_admin -from sponsors.forms import SponsorshipReviewAdminForm, SponsorBenefitAdminInlineForm, RequiredImgAssetConfigurationForm, \ - SponsorshipBenefitAdminForm, CloneApplicationConfigForm +from sponsors.forms import ( + SponsorshipReviewAdminForm, + SponsorBenefitAdminInlineForm, + RequiredImgAssetConfigurationForm, + SponsorshipBenefitAdminForm, + CloneApplicationConfigForm, +) from cms.admin import ContentManageableModelAdmin @@ -38,15 +47,12 @@ class AssetsInline(GenericTabularInline): has_delete_permission = lambda self, request, obj: False readonly_fields = ["internal_name", "user_submitted_info", "value"] - @admin.display( - description="Submitted information" - ) + @admin.display(description="Submitted information") def value(self, obj=None): if not obj or not obj.value: return "" return obj.value - @admin.display( description="Fullfilled data?", boolean=True, @@ -55,7 +61,6 @@ def user_submitted_info(self, obj=None): return bool(self.value(obj)) - @admin.register(SponsorshipProgram) class SponsorshipProgramAdmin(OrderedModelAdmin): ordering = ("order",) @@ -183,7 +188,10 @@ def update_related_sponsorships(self, *args, **kwargs): @admin.register(SponsorshipPackage) class SponsorshipPackageAdmin(OrderedModelAdmin): - ordering = ("-year", "order",) + ordering = ( + "-year", + "order", + ) list_display = ["name", "year", "advertise", "allow_a_la_carte", "get_benefit_split", "move_up_down_links"] list_filter = ["advertise", "year", "allow_a_la_carte"] search_fields = ["name"] @@ -198,7 +206,7 @@ def get_readonly_fields(self, request, obj=None): def get_prepopulated_fields(self, request, obj=None): if not obj: - return {'slug': ['name']} + return {"slug": ["name"]} return {} def get_benefit_split(self, obj: SponsorshipPackage) -> str: @@ -239,9 +247,7 @@ class SponsorshipsInline(admin.TabularInline): can_delete = False extra = 0 - @admin.display( - description="ID" - ) + @admin.display(description="ID") def link(self, obj): url = reverse("admin:sponsors_sponsorship_change", args=[obj.id]) return mark_safe(f"<a href={url}>{obj.id}</a>") @@ -278,7 +284,7 @@ def has_delete_permission(self, request, obj=None): return obj.open_for_editing def get_queryset(self, request): - #filters the available benefits by the benefits for the year of the sponsorship + # filters the available benefits by the benefits for the year of the sponsorship match = request.resolver_match sponsorship = self.parent_model.objects.get(pk=match.kwargs["object_id"]) year = sponsorship.year @@ -288,7 +294,7 @@ def get_queryset(self, request): class TargetableEmailBenefitsFilter(admin.SimpleListFilter): title = "targetable email benefits" - parameter_name = 'email_benefit' + parameter_name = "email_benefit" @cached_property def benefits(self): @@ -297,17 +303,14 @@ def benefits(self): return {str(b.id): b for b in benefits} def lookups(self, request, model_admin): - return [ - (k, b.name) for k, b in self.benefits.items() - ] + return [(k, b.name) for k, b in self.benefits.items()] def queryset(self, request, queryset): benefit = self.benefits.get(self.value()) if not benefit: return queryset # all sponsors benefit related with such sponsorship benefit - qs = SponsorBenefit.objects.filter( - sponsorship_benefit_id=benefit.id).values_list("sponsorship_id", flat=True) + qs = SponsorBenefit.objects.filter(sponsorship_benefit_id=benefit.id).values_list("sponsorship_id", flat=True) return queryset.filter(id__in=Subquery(qs)) @@ -328,40 +331,39 @@ def queryset(self, request, queryset): def choices(self, changelist): choices = list(super().choices(changelist)) # replaces django default "All" text by a custom text - choices[0]['display'] = "Applied / Approved / Finalized" + choices[0]["display"] = "Applied / Approved / Finalized" return choices class SponsorshipResource(resources.ModelResource): - - sponsor_name = Field(attribute='sponsor__name', column_name='Company Name') - contact_name = Field(column_name='Contact Name(s)') - contact_email = Field(column_name='Contact Email(s)') - contact_phone = Field(column_name='Contact phone number') - contact_type = Field(column_name='Contact Type(s)') - start_date = Field(attribute='start_date', column_name='Start Date') - end_date = Field(attribute='end_date', column_name='End Date') - web_logo = Field(column_name='Logo') - landing_page_url = Field(attribute='sponsor__landing_page_url', column_name='Webpage link') - level = Field(attribute='package__name', column_name='Sponsorship Level') - cost = Field(attribute='sponsorship_fee', column_name='Sponsorship Cost') - admin_url = Field(attribute='admin_url', column_name='Admin Link') + sponsor_name = Field(attribute="sponsor__name", column_name="Company Name") + contact_name = Field(column_name="Contact Name(s)") + contact_email = Field(column_name="Contact Email(s)") + contact_phone = Field(column_name="Contact phone number") + contact_type = Field(column_name="Contact Type(s)") + start_date = Field(attribute="start_date", column_name="Start Date") + end_date = Field(attribute="end_date", column_name="End Date") + web_logo = Field(column_name="Logo") + landing_page_url = Field(attribute="sponsor__landing_page_url", column_name="Webpage link") + level = Field(attribute="package__name", column_name="Sponsorship Level") + cost = Field(attribute="sponsorship_fee", column_name="Sponsorship Cost") + admin_url = Field(attribute="admin_url", column_name="Admin Link") class Meta: model = Sponsorship fields = ( - 'sponsor_name', - 'contact_name', - 'contact_email', - 'contact_phone', - 'contact_type', - 'start_date', - 'end_date', - 'web_logo', - 'landing_page_url', - 'level', - 'cost', - 'admin_url', + "sponsor_name", + "contact_name", + "contact_email", + "contact_phone", + "contact_type", + "start_date", + "end_date", + "web_logo", + "landing_page_url", + "level", + "cost", + "admin_url", ) export_order = ( "sponsor_name", @@ -381,7 +383,7 @@ class Meta: def get_sponsorship_url(self, sponsorship): domain = Site.objects.get_current().domain url = reverse("admin:sponsors_sponsorship_change", args=[sponsorship.id]) - return f'https://{domain}{url}' + return f"https://{domain}{url}" def dehydrate_web_logo(self, sponsorship): return sponsorship.sponsor.web_logo.url @@ -495,13 +497,10 @@ def get_queryset(self, *args, **kwargs): qs = super().get_queryset(*args, **kwargs) return qs.select_related("sponsor", "package", "submited_by") - @admin.action( - description='Send notifications to selected' - ) + @admin.action(description="Send notifications to selected") def send_notifications(self, request, queryset): return views_admin.send_sponsorship_notifications_action(self, request, queryset) - def get_readonly_fields(self, request, obj): readonly_fields = [ "for_modified_package", @@ -536,16 +535,12 @@ def get_readonly_fields(self, request, obj): return readonly_fields - @admin.display( - description="Sponsor" - ) + @admin.display(description="Sponsor") def sponsor_link(self, obj): url = reverse("admin:sponsors_sponsor_change", args=[obj.sponsor.id]) return mark_safe(f"<a href={url}>{obj.sponsor.name}</a>") - @admin.display( - description="Estimated cost" - ) + @admin.display(description="Estimated cost") def get_estimated_cost(self, obj): cost = None html = "This sponsorship has not customizations so there's no estimated cost" @@ -555,10 +550,7 @@ def get_estimated_cost(self, obj): html = f"{cost} USD <br/><b>Important: </b> {msg}" return mark_safe(html) - - @admin.display( - description="Contract" - ) + @admin.display(description="Contract") def get_contract(self, obj): if not obj.contract: return "---" @@ -566,7 +558,6 @@ def get_contract(self, obj): html = f"<a href='{url}' target='_blank'>{obj.contract}</a>" return mark_safe(html) - def get_urls(self): urls = super().get_urls() base_name = get_url_base_name(self.model) @@ -611,67 +602,45 @@ def get_urls(self): ] return my_urls + urls - @admin.display( - description="Name" - ) + @admin.display(description="Name") def get_sponsor_name(self, obj): return obj.sponsor.name - - @admin.display( - description="Description" - ) + @admin.display(description="Description") def get_sponsor_description(self, obj): return obj.sponsor.description - - @admin.display( - description="Landing Page URL" - ) + @admin.display(description="Landing Page URL") def get_sponsor_landing_page_url(self, obj): return obj.sponsor.landing_page_url - - @admin.display( - description="Web Logo" - ) + @admin.display(description="Web Logo") def get_sponsor_web_logo(self, obj): html = "{% load thumbnail %}{% thumbnail sponsor.web_logo '150x150' format='PNG' quality=100 as im %}<img src='{{ im.url}}'/>{% endthumbnail %}" template = Template(html) - context = Context({'sponsor': obj.sponsor}) + context = Context({"sponsor": obj.sponsor}) html = template.render(context) return mark_safe(html) - - @admin.display( - description="Print Logo" - ) + @admin.display(description="Print Logo") def get_sponsor_print_logo(self, obj): img = obj.sponsor.print_logo html = "" if img: html = "{% load thumbnail %}{% thumbnail img '150x150' format='PNG' quality=100 as im %}<img src='{{ im.url}}'/>{% endthumbnail %}" template = Template(html) - context = Context({'img': img}) + context = Context({"img": img}) html = template.render(context) return mark_safe(html) if html else "---" - - @admin.display( - description="Primary Phone" - ) + @admin.display(description="Primary Phone") def get_sponsor_primary_phone(self, obj): return obj.sponsor.primary_phone - - @admin.display( - description="Mailing/Billing Address" - ) + @admin.display(description="Mailing/Billing Address") def get_sponsor_mailing_address(self, obj): sponsor = obj.sponsor - city_row = ( - f"{sponsor.city} - {sponsor.get_country_display()} ({sponsor.country})" - ) + city_row = f"{sponsor.city} - {sponsor.get_country_display()} ({sponsor.country})" if sponsor.state: city_row = f"{sponsor.city} - {sponsor.state} - {sponsor.get_country_display()} ({sponsor.country})" @@ -684,10 +653,7 @@ def get_sponsor_mailing_address(self, obj): html += f"<p>{sponsor.postal_code}</p>" return mark_safe(html) - - @admin.display( - description="Contacts" - ) + @admin.display(description="Contacts") def get_sponsor_contacts(self, obj): html = "" contacts = obj.sponsor.contacts.all() @@ -695,47 +661,32 @@ def get_sponsor_contacts(self, obj): not_primary = [c for c in contacts if not c.primary] if primary: html = "<b>Primary contacts</b><ul>" - html += "".join( - [f"<li>{c.name}: {c.email} / {c.phone}</li>" for c in primary] - ) + html += "".join([f"<li>{c.name}: {c.email} / {c.phone}</li>" for c in primary]) html += "</ul>" if not_primary: html += "<b>Other contacts</b><ul>" - html += "".join( - [f"<li>{c.name}: {c.email} / {c.phone}</li>" for c in not_primary] - ) + html += "".join([f"<li>{c.name}: {c.email} / {c.phone}</li>" for c in not_primary]) html += "</ul>" return mark_safe(html) - - @admin.display( - description="Added by User" - ) + @admin.display(description="Added by User") def get_custom_benefits_added_by_user(self, obj): benefits = obj.user_customizations["added_by_user"] if not benefits: return "---" - html = "".join( - [f"<p>{b}</p>" for b in benefits] - ) + html = "".join([f"<p>{b}</p>" for b in benefits]) return mark_safe(html) - - @admin.display( - description="Removed by User" - ) + @admin.display(description="Removed by User") def get_custom_benefits_removed_by_user(self, obj): benefits = obj.user_customizations["removed_by_user"] if not benefits: return "---" - html = "".join( - [f"<p>{b}</p>" for b in benefits] - ) + html = "".join([f"<p>{b}</p>" for b in benefits]) return mark_safe(html) - def rollback_to_editing_view(self, request, pk): return views_admin.rollback_to_editing_view(self, request, pk) @@ -781,9 +732,7 @@ def get_urls(self): ] return my_urls + urls - @admin.display( - description="Links" - ) + @admin.display(description="Links") def links(self, obj): clone_form = CloneApplicationConfigForm() configured_years = clone_form.configured_years @@ -791,7 +740,7 @@ def links(self, obj): application_url = reverse("select_sponsorship_application_benefits") benefits_url = reverse("admin:sponsors_sponsorshipbenefit_changelist") packages_url = reverse("admin:sponsors_sponsorshippackage_changelist") - preview_label = 'View sponsorship application' + preview_label = "View sponsorship application" year = obj.year html = "<ul>" preview_querystring = f"config_year={year}" @@ -806,9 +755,7 @@ def links(self, obj): html += "</ul>" return mark_safe(html) - @admin.display( - description="Other configured years" - ) + @admin.display(description="Other configured years") def other_years(self, obj): clone_form = CloneApplicationConfigForm() configured_years = clone_form.configured_years @@ -822,7 +769,7 @@ def other_years(self, obj): application_url = reverse("select_sponsorship_application_benefits") benefits_url = reverse("admin:sponsors_sponsorshipbenefit_changelist") packages_url = reverse("admin:sponsors_sponsorshippackage_changelist") - preview_label = 'View sponsorship application form for this year' + preview_label = "View sponsorship application form for this year" html = "<ul>" for year in configured_years: preview_querystring = f"config_year={year}" @@ -843,6 +790,7 @@ def other_years(self, obj): def clone_application_config(self, request): return views_admin.clone_application_config(self, request) + @admin.register(LegalClause) class LegalClauseModelAdmin(OrderedModelAdmin): list_display = ["internal_name"] @@ -866,13 +814,10 @@ def get_queryset(self, *args, **kwargs): qs = super().get_queryset(*args, **kwargs) return qs.select_related("sponsorship__sponsor") - @admin.display( - description="Revision" - ) + @admin.display(description="Revision") def get_revision(self, obj): return obj.revision if obj.is_draft else "Final" - fieldsets = [ ( "Info", @@ -939,9 +884,7 @@ def get_readonly_fields(self, request, obj): return readonly_fields - @admin.display( - description="Contract document" - ) + @admin.display(description="Contract document") def document_link(self, obj): html, url, msg = "---", "", "" @@ -959,10 +902,7 @@ def document_link(self, obj): html = f'<a href="{url}" target="_blank">{msg}</a>' return mark_safe(html) - - @admin.display( - description="Sponsorship" - ) + @admin.display(description="Sponsorship") def get_sponsorship_url(self, obj): if not obj.sponsorship: return "---" @@ -970,7 +910,6 @@ def get_sponsorship_url(self, obj): html = f"<a href='{url}' target='_blank'>{obj.sponsorship}</a>" return mark_safe(html) - def get_urls(self): urls = super().get_urls() base_name = get_url_base_name(self.model) @@ -1013,7 +952,6 @@ def nullify_contract_view(self, request, pk): @admin.register(SponsorEmailNotificationTemplate) class SponsorEmailNotificationTemplateAdmin(BaseEmailTemplateAdmin): - def get_form(self, request, obj=None, **kwargs): help_texts = { "content": SPONSOR_TEMPLATE_HELP_TEXT, @@ -1024,7 +962,7 @@ def get_form(self, request, obj=None, **kwargs): class AssetTypeListFilter(admin.SimpleListFilter): title = "Asset Type" - parameter_name = 'type' + parameter_name = "type" @property def assets_types_mapping(self): @@ -1042,12 +980,15 @@ def queryset(self, request, queryset): class AssociatedBenefitListFilter(admin.SimpleListFilter): title = "From Benefit Which Requires Asset" - parameter_name = 'from_benefit' + parameter_name = "from_benefit" @property def benefits_with_assets(self): - qs = BenefitFeature.objects.required_assets().values_list("sponsor_benefit__sponsorship_benefit", - flat=True).distinct() + qs = ( + BenefitFeature.objects.required_assets() + .values_list("sponsor_benefit__sponsorship_benefit", flat=True) + .distinct() + ) benefits = SponsorshipBenefit.objects.filter(id__in=Subquery(qs)) return {str(b.id): b for b in benefits} @@ -1058,17 +999,13 @@ def queryset(self, request, queryset): benefit = self.benefits_with_assets.get(self.value()) if not benefit: return queryset - internal_names = [ - cfg.internal_name - for cfg in benefit.features_config.all() - if hasattr(cfg, "internal_name") - ] + internal_names = [cfg.internal_name for cfg in benefit.features_config.all() if hasattr(cfg, "internal_name")] return queryset.filter(internal_name__in=internal_names) class AssetContentTypeFilter(admin.SimpleListFilter): title = "Related Object" - parameter_name = 'content_type' + parameter_name = "content_type" def lookups(self, request, model_admin): qs = ContentType.objects.filter(model__in=["sponsorship", "sponsor"]) @@ -1105,8 +1042,12 @@ def queryset(self, request, queryset): @admin.register(GenericAsset) class GenericAssetModelAdmin(PolymorphicParentModelAdmin): list_display = ["id", "internal_name", "get_value", "content_type", "get_related_object"] - list_filter = [AssetContentTypeFilter, AssetTypeListFilter, AssetWithOrWithoutValueFilter, - AssociatedBenefitListFilter] + list_filter = [ + AssetContentTypeFilter, + AssetTypeListFilter, + AssetWithOrWithoutValueFilter, + AssociatedBenefitListFilter, + ] actions = ["export_assets_as_zipfile"] def get_child_models(self, *args, **kwargs): @@ -1117,8 +1058,8 @@ def get_queryset(self, *args, **kwargs): def get_actions(self, request): actions = super().get_actions(request) - if 'delete_selected' in actions: - del actions['delete_selected'] + if "delete_selected" in actions: + del actions["delete_selected"] return actions def has_add_permission(self, *args, **kwargs): @@ -1134,19 +1075,14 @@ def all_sponsorships(self): qs = Sponsorship.objects.all().select_related("package", "sponsor") return {sp.id: sp for sp in qs} - @admin.display( - description="Value" - ) + @admin.display(description="Value") def get_value(self, obj): html = obj.value if obj.value and getattr(obj.value, "url", None): html = f"<a href='{obj.value.url}' target='_blank'>{obj.value}</a>" return mark_safe(html) - - @admin.display( - description="Associated with" - ) + @admin.display(description="Associated with") def get_related_object(self, obj): """ Returns the content_object as an URL and performs better because @@ -1164,16 +1100,14 @@ def get_related_object(self, obj): html = f"<a href='{content_object.admin_url}' target='_blank'>{content_object}</a>" return mark_safe(html) - - @admin.action( - description="Export selected" - ) + @admin.action(description="Export selected") def export_assets_as_zipfile(self, request, queryset): return views_admin.export_assets_as_zipfile(self, request, queryset) class GenericAssetChildModelAdmin(PolymorphicChildModelAdmin): - """ Base admin class for all GenericAsset child models """ + """Base admin class for all GenericAsset child models""" + base_model = GenericAsset readonly_fields = ["uuid", "content_type", "object_id", "content_object", "internal_name"] diff --git a/sponsors/api.py b/sponsors/api.py index 0d180be6d..c7598e073 100644 --- a/sponsors/api.py +++ b/sponsors/api.py @@ -5,12 +5,16 @@ from rest_framework.views import APIView from rest_framework.response import Response from sponsors.models import BenefitFeature, LogoPlacement, Sponsorship, GenericAsset -from sponsors.serializers import LogoPlacementSerializer, FilterLogoPlacementsSerializer, FilterAssetsSerializer, \ - AssetSerializer +from sponsors.serializers import ( + LogoPlacementSerializer, + FilterLogoPlacementsSerializer, + FilterAssetsSerializer, + AssetSerializer, +) class SponsorPublisherPermission(permissions.BasePermission): - message = 'Must have publisher permission.' + message = "Must have publisher permission." def has_permission(self, request, view): user = request.user @@ -50,9 +54,13 @@ def get(self, request, *args, **kwargs): placement["publisher"] = logo.publisher placement["flight"] = logo.logo_place if logo.describe_as_sponsor: - placement["description"] = f"{sponsor.name} is a {sponsorship.level_name} sponsor of the Python Software Foundation." + placement["description"] = ( + f"{sponsor.name} is a {sponsorship.level_name} sponsor of the Python Software Foundation." + ) if logo.link_to_sponsors_page: - placement["sponsor_url"] = request.build_absolute_uri(reverse('psf-sponsors') + f"#{slugify(sponsor.name)}") + placement["sponsor_url"] = request.build_absolute_uri( + reverse("psf-sponsors") + f"#{slugify(sponsor.name)}" + ) placements.append(placement) serializer = LogoPlacementSerializer(placements, many=True) @@ -66,8 +74,7 @@ def get(self, request, *args, **kwargs): assets_filter = FilterAssetsSerializer(data=request.GET) assets_filter.is_valid(raise_exception=True) - assets = GenericAsset.objects.all_assets().filter( - internal_name=assets_filter.by_internal_name).iterator() + assets = GenericAsset.objects.all_assets().filter(internal_name=assets_filter.by_internal_name).iterator() assets = (a for a in assets if assets_filter.accept_empty or a.has_value) serializer = AssetSerializer(assets, many=True) diff --git a/sponsors/apps.py b/sponsors/apps.py index 0eca9c16e..3209d9102 100644 --- a/sponsors/apps.py +++ b/sponsors/apps.py @@ -2,5 +2,4 @@ class SponsorsAppConfig(AppConfig): - - name = 'sponsors' + name = "sponsors" diff --git a/sponsors/contracts.py b/sponsors/contracts.py index e0fd75b6c..0652a55c4 100644 --- a/sponsors/contracts.py +++ b/sponsors/contracts.py @@ -14,11 +14,7 @@ def _clean_split(text, separator="\n"): - return [ - t.replace("-", "").strip() - for t in text.split("\n") - if t.replace("-", "").strip() - ] + return [t.replace("-", "").strip() for t in text.split("\n") if t.replace("-", "").strip()] def _contract_context(contract, **context): @@ -48,9 +44,7 @@ def render_markdown_from_template(contract, **context): def render_contract_to_pdf_response(request, contract, **context): - response = HttpResponse( - render_contract_to_pdf_file(contract, **context), content_type="application/pdf" - ) + response = HttpResponse(render_contract_to_pdf_file(contract, **context), content_type="application/pdf") return response @@ -58,9 +52,7 @@ def render_contract_to_pdf_file(contract, **context): with tempfile.NamedTemporaryFile() as docx_file: with tempfile.NamedTemporaryFile(suffix=".pdf") as pdf_file: markdown = render_markdown_from_template(contract, **context) - pdf = pypandoc.convert_text( - markdown, "pdf", outputfile=pdf_file.name, format="md" - ) + pdf = pypandoc.convert_text(markdown, "pdf", outputfile=pdf_file.name, format="md") return pdf_file.read() @@ -69,9 +61,9 @@ def render_contract_to_docx_response(request, contract, **context): render_contract_to_docx_file(contract, **context), content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document", ) - response[ - "Content-Disposition" - ] = f"attachment; filename={'sponsorship-renewal' if contract.sponsorship.renewal else 'sponsorship-contract'}-{unidecode(contract.sponsorship.sponsor.name.replace(' ', '-').replace('.', ''))}.docx" + response["Content-Disposition"] = ( + f"attachment; filename={'sponsorship-renewal' if contract.sponsorship.renewal else 'sponsorship-contract'}-{unidecode(contract.sponsorship.sponsor.name.replace(' ', '-').replace('.', ''))}.docx" + ) return response diff --git a/sponsors/forms.py b/sponsors/forms.py index 4ced017c9..834be5039 100644 --- a/sponsors/forms.py +++ b/sponsors/forms.py @@ -23,11 +23,12 @@ SponsorEmailNotificationTemplate, RequiredImgAssetConfiguration, BenefitFeature, - SPONSOR_TEMPLATE_HELP_TEXT, SponsorshipCurrentYear, + SPONSOR_TEMPLATE_HELP_TEXT, + SponsorshipCurrentYear, ) SPONSORSHIP_YEAR_SELECT = forms.Select( - choices=(((None, '---'),) + tuple(((y, str(y)) for y in range(2021, datetime.date.today().year + 2)))) + choices=(((None, "---"),) + tuple(((y, str(y)) for y in range(2021, datetime.date.today().year + 2)))) ) @@ -79,9 +80,7 @@ def __init__(self, *args, **kwargs): queryset=SponsorshipBenefit.objects.from_year(year).standalone().select_related("program"), ) - benefits_qs = SponsorshipBenefit.objects.from_year(year).with_packages().select_related( - "program" - ) + benefits_qs = SponsorshipBenefit.objects.from_year(year).with_packages().select_related("program") for program in SponsorshipProgram.objects.all(): slug = slugify(program.name).replace("-", "_") @@ -109,9 +108,7 @@ def benefits_conflicts(self): def get_benefits(self, cleaned_data=None, include_a_la_carte=False, include_standalone=False): cleaned_data = cleaned_data or self.cleaned_data - benefits = list( - chain(*(cleaned_data.get(bp.name) for bp in self.benefits_programs)) - ) + benefits = list(chain(*(cleaned_data.get(bp.name) for bp in self.benefits_programs))) a_la_carte = cleaned_data.get("a_la_carte_benefits", []) if include_a_la_carte: benefits.extend([b for b in a_la_carte]) @@ -147,48 +144,32 @@ def _clean_benefits(self, cleaned_data): standalone = cleaned_data.get("standalone_benefits") if not benefits and not standalone: - raise forms.ValidationError( - _("You have to pick a minimum number of benefits.") - ) + raise forms.ValidationError(_("You have to pick a minimum number of benefits.")) elif benefits and not package: - raise forms.ValidationError( - _("You must pick a package to include the selected benefits.") - ) + raise forms.ValidationError(_("You must pick a package to include the selected benefits.")) elif standalone and package: - raise forms.ValidationError( - _("Application with package cannot have standalone benefits.") - ) + raise forms.ValidationError(_("Application with package cannot have standalone benefits.")) elif package and a_la_carte and not package.allow_a_la_carte: - raise forms.ValidationError( - _("Package does not accept a la carte benefits.") - ) + raise forms.ValidationError(_("Package does not accept a la carte benefits.")) benefits_ids = [b.id for b in benefits] for benefit in benefits: conflicts = set(self.benefits_conflicts.get(benefit.id, [])) if conflicts and set(benefits_ids).intersection(conflicts): - raise forms.ValidationError( - _("The application has 1 or more benefits that conflicts.") - ) + raise forms.ValidationError(_("The application has 1 or more benefits that conflicts.")) if benefit.package_only: if not package: raise forms.ValidationError( - _( - "The application has 1 or more package only benefits and no sponsor package." - ) + _("The application has 1 or more package only benefits and no sponsor package.") ) elif not benefit.packages.filter(id=package.id).exists(): raise forms.ValidationError( - _( - "The application has 1 or more package only benefits but wrong sponsor package." - ) + _("The application has 1 or more package only benefits but wrong sponsor package.") ) if not benefit.has_capacity: - raise forms.ValidationError( - _("The application has 1 or more benefits with no capacity.") - ) + raise forms.ValidationError(_("The application has 1 or more benefits with no capacity.")) return cleaned_data @@ -235,7 +216,7 @@ class SponsorshipApplicationForm(forms.Form): label="Sponsor print logo", help_text="For printed materials, signage, and projection. SVG or EPS", required=False, - validators=[FileExtensionValidator(['eps', 'epsf' 'epsi', 'svg', 'png'])], + validators=[FileExtensionValidator(["eps", "epsf" "epsi", "svg", "png"])], ) primary_phone = forms.CharField( @@ -255,15 +236,14 @@ class SponsorshipApplicationForm(forms.Form): ) city = forms.CharField(max_length=64, required=False) - state = forms.CharField( - label="State/Province/Region", max_length=64, required=False - ) + state = forms.CharField(label="State/Province/Region", max_length=64, required=False) state_of_incorporation = forms.CharField( - label="State of incorporation", help_text="US only, If different than mailing address", max_length=64, required=False - ) - postal_code = forms.CharField( - label="Zip/Postal Code", max_length=64, required=False + label="State of incorporation", + help_text="US only, If different than mailing address", + max_length=64, + required=False, ) + postal_code = forms.CharField(label="Zip/Postal Code", max_length=64, required=False) country = CountryField().formfield(required=False, help_text="For mailing/contact purposes") country_of_incorporation = CountryField().formfield( @@ -275,9 +255,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) qs = Sponsor.objects.none() if self.user: - sponsor_ids = SponsorContact.objects.filter(user=self.user).values_list( - "sponsor", flat=True - ) + sponsor_ids = SponsorContact.objects.filter(user=self.user).values_list("sponsor", flat=True) qs = Sponsor.objects.filter(id__in=sponsor_ids) self.fields["sponsor"] = forms.ModelChoiceField(queryset=qs, required=False) @@ -296,9 +274,7 @@ def clean(self): msg = "You have to enter at least one contact" raise forms.ValidationError(msg) elif not sponsor: - has_primary_contact = any( - f.cleaned_data.get("primary") for f in self.contacts_formset.forms - ) + has_primary_contact = any(f.cleaned_data.get("primary") for f in self.contacts_formset.forms) if not has_primary_contact: msg = "You have to mark at least one contact as the primary one." raise forms.ValidationError(msg) @@ -408,7 +384,9 @@ def user_with_previous_sponsors(self): class SponsorshipReviewAdminForm(forms.ModelForm): start_date = forms.DateField(widget=AdminDateWidget(), required=False) end_date = forms.DateField(widget=AdminDateWidget(), required=False) - overlapped_by = forms.ModelChoiceField(queryset=Sponsorship.objects.select_related("sponsor", "package"), required=False) + overlapped_by = forms.ModelChoiceField( + queryset=Sponsorship.objects.select_related("sponsor", "package"), required=False + ) renewal = forms.BooleanField( help_text="If true, it means the sponsorship is a renewal of a previous sponsorship and will use the renewal template for contracting.", required=False, @@ -426,12 +404,11 @@ def __init__(self, *args, **kwargs): self.fields[field_name].required = True self.fields["renewal"].required = False - class Meta: model = Sponsorship fields = ["start_date", "end_date", "package", "sponsorship_fee", "renewal"] widgets = { - 'year': SPONSORSHIP_YEAR_SELECT, + "year": SPONSORSHIP_YEAR_SELECT, } def clean(self): @@ -450,12 +427,13 @@ class SignedSponsorshipReviewAdminForm(SponsorshipReviewAdminForm): """ Form to approve sponsorships that already have a signed contract """ + signed_contract = forms.FileField(help_text="Please upload the final version of the signed contract.") class SponsorBenefitAdminInlineForm(forms.ModelForm): sponsorship_benefit = forms.ModelChoiceField( - queryset=SponsorshipBenefit.objects.order_by('program', 'order').select_related("program"), + queryset=SponsorshipBenefit.objects.order_by("program", "order").select_related("program"), required=False, ) @@ -578,7 +556,7 @@ class SponsorUpdateForm(forms.ModelForm): widget=forms.widgets.FileInput, help_text="For printed materials, signage, and projection. SVG or EPS", required=False, - validators=[FileExtensionValidator(['eps', 'epsf' 'epsi', 'svg', 'png'])], + validators=[FileExtensionValidator(["eps", "epsf" "epsi", "svg", "png"])], ) def __init__(self, *args, **kwargs): @@ -601,7 +579,7 @@ def __init__(self, *args, **kwargs): self.contacts_formset = factory(**formset_kwargs) # display fields as read-only for disabled in self.READONLY_FIELDS: - self.fields[disabled].widget.attrs['readonly'] = True + self.fields[disabled].widget.attrs["readonly"] = True class Meta: exclude = ["created", "updated", "creator", "last_modified_by"] @@ -616,9 +594,7 @@ def clean(self): msg = "You have to enter at least one contact" raise forms.ValidationError(msg) - has_primary_contact = any( - f.cleaned_data.get("primary") for f in self.contacts_formset.forms - ) + has_primary_contact = any(f.cleaned_data.get("primary") for f in self.contacts_formset.forms) if not has_primary_contact: msg = "You have to mark at least one contact as the primary one." raise forms.ValidationError(msg) @@ -629,7 +605,6 @@ def save(self, *args, **kwargs): class RequiredImgAssetConfigurationForm(forms.ModelForm): - def clean(self): data = super().clean() @@ -683,7 +658,9 @@ def __init__(self, *args, **kwargs): field = required_asset.as_form_field(required=required, initial=value) if required_asset.due_date and not bool(value): - field.label = mark_safe(f"<big><b>{field.label}</b></big><br><b>(Required by {required_asset.due_date})</b>") + field.label = mark_safe( + f"<big><b>{field.label}</b></big><br><b>(Required by {required_asset.due_date})</b>" + ) if bool(value): field.label = mark_safe(f"<big><b>{field.label}</b></big><br><small>(Fulfilled, thank you!)</small>") @@ -712,11 +689,10 @@ def has_input(self): class SponsorshipBenefitAdminForm(forms.ModelForm): - class Meta: model = SponsorshipBenefit widgets = { - 'year': SPONSORSHIP_YEAR_SELECT, + "year": SPONSORSHIP_YEAR_SELECT, } fields = "__all__" @@ -735,13 +711,10 @@ def clean(self): class CloneApplicationConfigForm(forms.Form): from_year = forms.ChoiceField( - required=True, - help_text="From which year you want to clone the benefits and packages.", - choices=[] + required=True, help_text="From which year you want to clone the benefits and packages.", choices=[] ) target_year = forms.IntegerField( - required=True, - help_text="The year of the resulting new sponsorship application configuration." + required=True, help_text="The year of the resulting new sponsorship application configuration." ) def __init__(self, *args, **kwargs): diff --git a/sponsors/management/commands/check_sponsorship_assets_due_date.py b/sponsors/management/commands/check_sponsorship_assets_due_date.py index 0f980fc90..c35acc97d 100644 --- a/sponsors/management/commands/check_sponsorship_assets_due_date.py +++ b/sponsors/management/commands/check_sponsorship_assets_due_date.py @@ -13,12 +13,15 @@ class Command(BaseCommand): This command will query for the sponsorships which have any required asset with a due date expiring within the certain amount of days """ + help = "Send notifications to sponsorship with pending required assets" def add_arguments(self, parser): help = "Num of days to be used as interval up to target date" parser.add_argument("num_days", nargs="?", default="7", help=help) - parser.add_argument("--no-input", action="store_true", help="Tells Django to NOT prompt the user for input of any kind.") + parser.add_argument( + "--no-input", action="store_true", help="Tells Django to NOT prompt the user for input of any kind." + ) def handle(self, **options): num_days = options["num_days"] @@ -32,11 +35,9 @@ def handle(self, **options): sponsorships_to_notify = [] for sponsorship in sponsorships: - to_notify = any([ - asset.due_date == target_date - for asset in req_assets.from_sponsorship(sponsorship) - if asset.due_date - ]) + to_notify = any( + [asset.due_date == target_date for asset in req_assets.from_sponsorship(sponsorship) if asset.due_date] + ) if to_notify: sponsorships_to_notify.append(sponsorship) @@ -46,8 +47,10 @@ def handle(self, **options): user_input = "" while user_input != "Y" and ask_input: - msg = f"Contacts from {len(sponsorships_to_notify)} with pending assets with expiring due date will get " \ - f"notified. " + msg = ( + f"Contacts from {len(sponsorships_to_notify)} with pending assets with expiring due date will get " + f"notified. " + ) msg += "Do you want to proceed? [Y/n]: " user_input = input(msg).strip().upper() if user_input == "N": diff --git a/sponsors/management/commands/create_contracts.py b/sponsors/management/commands/create_contracts.py index 16bc986e0..a0c746a83 100644 --- a/sponsors/management/commands/create_contracts.py +++ b/sponsors/management/commands/create_contracts.py @@ -12,6 +12,7 @@ # The same limitation is true for the SponsorshipQuerySet's approved method and for # the sponsorship.contract reverse lookup. + class Command(BaseCommand): """ Create Contract objects for existing approved Sponsorships. @@ -19,6 +20,7 @@ class Command(BaseCommand): Run this command as a initial data migration or to make sure all approved Sponsorships do have associated Contract objects. """ + help = "Create Contract objects for existing approved Sponsorships." def handle(self, **options): diff --git a/sponsors/management/commands/create_pycon_vouchers_for_sponsors.py b/sponsors/management/commands/create_pycon_vouchers_for_sponsors.py index 3e3b4973d..23339512e 100644 --- a/sponsors/management/commands/create_pycon_vouchers_for_sponsors.py +++ b/sponsors/management/commands/create_pycon_vouchers_for_sponsors.py @@ -79,18 +79,14 @@ def generate_voucher_codes(year): .all() ): try: - quantity = BenefitFeature.objects.instance_of(TieredBenefit).get( - sponsor_benefit=sponsorbenefit - ) + quantity = BenefitFeature.objects.instance_of(TieredBenefit).get(sponsor_benefit=sponsorbenefit) except BenefitFeature.DoesNotExist: - print( - f"No quantity found for {sponsorbenefit.sponsorship.sponsor.name} and {code['internal_name']}" - ) + print(f"No quantity found for {sponsorbenefit.sponsorship.sponsor.name} and {code['internal_name']}") continue try: - asset = ProvidedTextAsset.objects.filter( - sponsor_benefit=sponsorbenefit - ).get(internal_name=code["internal_name"]) + asset = ProvidedTextAsset.objects.filter(sponsor_benefit=sponsorbenefit).get( + internal_name=code["internal_name"] + ) except ProvidedTextAsset.DoesNotExist: print( f"No provided asset found for {sponsorbenefit.sponsorship.sponsor.name} with internal name {code['internal_name']}" diff --git a/sponsors/management/commands/fullfill_pycon_2022.py b/sponsors/management/commands/fullfill_pycon_2022.py index 86ee26a1e..4b5a5877a 100644 --- a/sponsors/management/commands/fullfill_pycon_2022.py +++ b/sponsors/management/commands/fullfill_pycon_2022.py @@ -77,18 +77,14 @@ def handle(self, **options): .all() ): try: - quantity = BenefitFeature.objects.instance_of(TieredBenefit).get( - sponsor_benefit=sponsorbenefit - ) + quantity = BenefitFeature.objects.instance_of(TieredBenefit).get(sponsor_benefit=sponsorbenefit) except BenefitFeature.DoesNotExist: - print( - f"No quantity found for {sponsorbenefit.sponsorship.sponsor.name} and {code_type}" - ) + print(f"No quantity found for {sponsorbenefit.sponsorship.sponsor.name} and {code_type}") continue try: - asset = ProvidedTextAsset.objects.filter( - sponsor_benefit=sponsorbenefit - ).get(internal_name=f"{code_type}_code") + asset = ProvidedTextAsset.objects.filter(sponsor_benefit=sponsorbenefit).get( + internal_name=f"{code_type}_code" + ) except ProvidedTextAsset.DoesNotExist: print( f"No provided asset found for {sponsorbenefit.sponsorship.sponsor.name} with internal name {code_type}_code" @@ -104,9 +100,7 @@ def handle(self, **options): }, ) if result["code"] == 200: - print( - f"Fullfilling {code_type} for {sponsorbenefit.sponsorship.sponsor.name}: {quantity.quantity}" - ) + print(f"Fullfilling {code_type} for {sponsorbenefit.sponsorship.sponsor.name}: {quantity.quantity}") promo_code = result["data"]["promo_code"] asset.value = promo_code asset.save() diff --git a/sponsors/migrations/0001_initial.py b/sponsors/migrations/0001_initial.py index 2908c1172..738c5a3af 100644 --- a/sponsors/migrations/0001_initial.py +++ b/sponsors/migrations/0001_initial.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ("companies", "0001_initial"), @@ -26,9 +25,7 @@ class Migration(migrations.Migration): ), ( "created", - models.DateTimeField( - db_index=True, default=django.utils.timezone.now, blank=True - ), + models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True), ), ( "updated", diff --git a/sponsors/migrations/0002_auto_20150416_1853.py b/sponsors/migrations/0002_auto_20150416_1853.py index 69f8631c3..88e4b4e6e 100644 --- a/sponsors/migrations/0002_auto_20150416_1853.py +++ b/sponsors/migrations/0002_auto_20150416_1853.py @@ -2,7 +2,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0001_initial"), ] diff --git a/sponsors/migrations/0003_auto_20170821_2000.py b/sponsors/migrations/0003_auto_20170821_2000.py index 6333e080a..14e4ac490 100644 --- a/sponsors/migrations/0003_auto_20170821_2000.py +++ b/sponsors/migrations/0003_auto_20170821_2000.py @@ -2,7 +2,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0002_auto_20150416_1853"), ] diff --git a/sponsors/migrations/0004_auto_20201014_1622.py b/sponsors/migrations/0004_auto_20201014_1622.py index 5fc1ecbc0..cd87125e5 100644 --- a/sponsors/migrations/0004_auto_20201014_1622.py +++ b/sponsors/migrations/0004_auto_20201014_1622.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0003_auto_20170821_2000"), ] @@ -25,9 +24,7 @@ class Migration(migrations.Migration): ), ( "order", - models.PositiveIntegerField( - db_index=True, editable=False, verbose_name="order" - ), + models.PositiveIntegerField(db_index=True, editable=False, verbose_name="order"), ), ("name", models.CharField(max_length=64)), ("description", models.TextField(blank=True, null=True)), @@ -59,9 +56,7 @@ class Migration(migrations.Migration): ), ( "order", - models.PositiveIntegerField( - db_index=True, editable=False, verbose_name="order" - ), + models.PositiveIntegerField(db_index=True, editable=False, verbose_name="order"), ), ("name", models.CharField(max_length=64)), ("sponsorship_amount", models.PositiveIntegerField()), @@ -85,9 +80,7 @@ class Migration(migrations.Migration): ), ( "order", - models.PositiveIntegerField( - db_index=True, editable=False, verbose_name="order" - ), + models.PositiveIntegerField(db_index=True, editable=False, verbose_name="order"), ), ("name", models.CharField(max_length=64)), ("description", models.TextField(blank=True, null=True)), @@ -100,9 +93,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name="sponsorshipbenefit", name="levels", - field=models.ManyToManyField( - related_name="benefits", to="sponsors.SponsorshipLevel" - ), + field=models.ManyToManyField(related_name="benefits", to="sponsors.SponsorshipLevel"), ), migrations.AddField( model_name="sponsorshipbenefit", diff --git a/sponsors/migrations/0005_auto_20201015_0908.py b/sponsors/migrations/0005_auto_20201015_0908.py index e9e962847..e6885126f 100644 --- a/sponsors/migrations/0005_auto_20201015_0908.py +++ b/sponsors/migrations/0005_auto_20201015_0908.py @@ -5,15 +5,12 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0004_auto_20201014_1622"), ] operations = [ - migrations.RenameField( - model_name="sponsorshipbenefit", old_name="value", new_name="internal_value" - ), + migrations.RenameField(model_name="sponsorshipbenefit", old_name="value", new_name="internal_value"), migrations.AddField( model_name="sponsorshipbenefit", name="capacity", diff --git a/sponsors/migrations/0006_auto_20201016_1517.py b/sponsors/migrations/0006_auto_20201016_1517.py index ff9d13754..d40356a20 100644 --- a/sponsors/migrations/0006_auto_20201016_1517.py +++ b/sponsors/migrations/0006_auto_20201016_1517.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0005_auto_20201015_0908"), ] diff --git a/sponsors/migrations/0007_auto_20201021_1410.py b/sponsors/migrations/0007_auto_20201021_1410.py index 06350db64..37015a47e 100644 --- a/sponsors/migrations/0007_auto_20201021_1410.py +++ b/sponsors/migrations/0007_auto_20201021_1410.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0006_auto_20201016_1517"), ] diff --git a/sponsors/migrations/0008_auto_20201028_1814.py b/sponsors/migrations/0008_auto_20201028_1814.py index 5a527fb08..28c84cb81 100644 --- a/sponsors/migrations/0008_auto_20201028_1814.py +++ b/sponsors/migrations/0008_auto_20201028_1814.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ("sponsors", "0007_auto_20201021_1410"), @@ -144,9 +143,7 @@ class Migration(migrations.Migration): ), ( "primary_phone", - models.CharField( - max_length=32, verbose_name="Sponsor Primary Phone" - ), + models.CharField(max_length=32, verbose_name="Sponsor Primary Phone"), ), ( "mailing_address", diff --git a/sponsors/migrations/0009_auto_20201103_1259.py b/sponsors/migrations/0009_auto_20201103_1259.py index 57886f522..180bb0cf0 100644 --- a/sponsors/migrations/0009_auto_20201103_1259.py +++ b/sponsors/migrations/0009_auto_20201103_1259.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0008_auto_20201028_1814"), ] diff --git a/sponsors/migrations/0010_auto_20201103_1313.py b/sponsors/migrations/0010_auto_20201103_1313.py index 8edc9ed04..e75213e69 100644 --- a/sponsors/migrations/0010_auto_20201103_1313.py +++ b/sponsors/migrations/0010_auto_20201103_1313.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0009_auto_20201103_1259"), ] @@ -38,9 +37,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name="sponsor", name="mailing_address", - field=models.TextField( - default="", verbose_name="Sponsor Mailing/Billing Address" - ), + field=models.TextField(default="", verbose_name="Sponsor Mailing/Billing Address"), preserve_default=False, ), migrations.AddField( @@ -57,9 +54,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name="sponsor", name="primary_phone", - field=models.CharField( - default="", max_length=32, verbose_name="Sponsor Primary Phone" - ), + field=models.CharField(default="", max_length=32, verbose_name="Sponsor Primary Phone"), preserve_default=False, ), migrations.AddField( diff --git a/sponsors/migrations/0011_auto_20201111_1724.py b/sponsors/migrations/0011_auto_20201111_1724.py index 140838caf..aba119ffb 100644 --- a/sponsors/migrations/0011_auto_20201111_1724.py +++ b/sponsors/migrations/0011_auto_20201111_1724.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0010_auto_20201103_1313"), ] diff --git a/sponsors/migrations/0012_sponsorship_for_modified_package.py b/sponsors/migrations/0012_sponsorship_for_modified_package.py index 6804ede95..6780671c8 100644 --- a/sponsors/migrations/0012_sponsorship_for_modified_package.py +++ b/sponsors/migrations/0012_sponsorship_for_modified_package.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0011_auto_20201111_1724"), ] diff --git a/sponsors/migrations/0013_sponsorbenefit_benefit_internal_value.py b/sponsors/migrations/0013_sponsorbenefit_benefit_internal_value.py index 743c8f68d..8d436cecb 100644 --- a/sponsors/migrations/0013_sponsorbenefit_benefit_internal_value.py +++ b/sponsors/migrations/0013_sponsorbenefit_benefit_internal_value.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0012_sponsorship_for_modified_package"), ] diff --git a/sponsors/migrations/0014_auto_20201116_1437.py b/sponsors/migrations/0014_auto_20201116_1437.py index 4b8c6b592..f7bd6cff6 100644 --- a/sponsors/migrations/0014_auto_20201116_1437.py +++ b/sponsors/migrations/0014_auto_20201116_1437.py @@ -18,13 +18,8 @@ def reset_sponsor_benefits_cost(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0013_sponsorbenefit_benefit_internal_value"), ] - operations = [ - migrations.RunPython( - populate_sponsor_benefits_cost, reset_sponsor_benefits_cost - ) - ] + operations = [migrations.RunPython(populate_sponsor_benefits_cost, reset_sponsor_benefits_cost)] diff --git a/sponsors/migrations/0015_auto_20201117_1739.py b/sponsors/migrations/0015_auto_20201117_1739.py index 47a8f147c..25b45500b 100644 --- a/sponsors/migrations/0015_auto_20201117_1739.py +++ b/sponsors/migrations/0015_auto_20201117_1739.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ("sponsors", "0014_auto_20201116_1437"), diff --git a/sponsors/migrations/0016_auto_20201119_1448.py b/sponsors/migrations/0016_auto_20201119_1448.py index 334ea8381..74e97f9c9 100644 --- a/sponsors/migrations/0016_auto_20201119_1448.py +++ b/sponsors/migrations/0016_auto_20201119_1448.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0015_auto_20201117_1739"), ] @@ -28,9 +27,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name="sponsor", name="mailing_address_line_1", - field=models.CharField( - default="", max_length=128, verbose_name="Mailing Address line 1" - ), + field=models.CharField(default="", max_length=128, verbose_name="Mailing Address line 1"), ), migrations.AddField( model_name="sponsor", @@ -45,9 +42,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name="sponsor", name="postal_code", - field=models.CharField( - default="", max_length=64, verbose_name="Zip/Postal Code" - ), + field=models.CharField(default="", max_length=64, verbose_name="Zip/Postal Code"), ), migrations.AddField( model_name="sponsor", diff --git a/sponsors/migrations/0017_sponsorbenefit_added_by_user.py b/sponsors/migrations/0017_sponsorbenefit_added_by_user.py index f304cd76b..f046b024f 100644 --- a/sponsors/migrations/0017_sponsorbenefit_added_by_user.py +++ b/sponsors/migrations/0017_sponsorbenefit_added_by_user.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0016_auto_20201119_1448"), ] diff --git a/sponsors/migrations/0018_auto_20201201_1659.py b/sponsors/migrations/0018_auto_20201201_1659.py index dfeca1571..0990b988d 100644 --- a/sponsors/migrations/0018_auto_20201201_1659.py +++ b/sponsors/migrations/0018_auto_20201201_1659.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0017_sponsorbenefit_added_by_user"), ] @@ -24,9 +23,7 @@ class Migration(migrations.Migration): ), ( "order", - models.PositiveIntegerField( - db_index=True, editable=False, verbose_name="order" - ), + models.PositiveIntegerField(db_index=True, editable=False, verbose_name="order"), ), ( "internal_name", diff --git a/sponsors/migrations/0019_sponsor_twitter_handle.py b/sponsors/migrations/0019_sponsor_twitter_handle.py index 4ad486123..8a51d294d 100644 --- a/sponsors/migrations/0019_sponsor_twitter_handle.py +++ b/sponsors/migrations/0019_sponsor_twitter_handle.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0018_auto_20201201_1659"), ] diff --git a/sponsors/migrations/0019_statementofwork.py b/sponsors/migrations/0019_statementofwork.py index e451ee8f2..3dd996c80 100644 --- a/sponsors/migrations/0019_statementofwork.py +++ b/sponsors/migrations/0019_statementofwork.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0019_sponsor_twitter_handle"), ] diff --git a/sponsors/migrations/0020_auto_20201210_1802.py b/sponsors/migrations/0020_auto_20201210_1802.py index c4c4fdd21..93663e1b6 100644 --- a/sponsors/migrations/0020_auto_20201210_1802.py +++ b/sponsors/migrations/0020_auto_20201210_1802.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0019_statementofwork"), ] @@ -17,9 +16,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name="sponsorbenefit", name="order", - field=models.PositiveIntegerField( - db_index=True, default=1, editable=False, verbose_name="order" - ), + field=models.PositiveIntegerField(db_index=True, default=1, editable=False, verbose_name="order"), preserve_default=False, ), ] diff --git a/sponsors/migrations/0020_sponsorshipbenefit_unavailable.py b/sponsors/migrations/0020_sponsorshipbenefit_unavailable.py index 35c842d1e..9e14a110f 100644 --- a/sponsors/migrations/0020_sponsorshipbenefit_unavailable.py +++ b/sponsors/migrations/0020_sponsorshipbenefit_unavailable.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0019_sponsor_twitter_handle"), ] diff --git a/sponsors/migrations/0021_auto_20201211_2120.py b/sponsors/migrations/0021_auto_20201211_2120.py index 87c076f14..86cbad219 100644 --- a/sponsors/migrations/0021_auto_20201211_2120.py +++ b/sponsors/migrations/0021_auto_20201211_2120.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0020_auto_20201210_1802"), ] diff --git a/sponsors/migrations/0022_sponsorcontact_administrative.py b/sponsors/migrations/0022_sponsorcontact_administrative.py index 3872f16b5..048e6ef7d 100644 --- a/sponsors/migrations/0022_sponsorcontact_administrative.py +++ b/sponsors/migrations/0022_sponsorcontact_administrative.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0021_auto_20201211_2120"), ] diff --git a/sponsors/migrations/0023_merge_20210406_1522.py b/sponsors/migrations/0023_merge_20210406_1522.py index 6280b3f30..70787c055 100644 --- a/sponsors/migrations/0023_merge_20210406_1522.py +++ b/sponsors/migrations/0023_merge_20210406_1522.py @@ -4,11 +4,9 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0022_sponsorcontact_administrative'), - ('sponsors', '0020_sponsorshipbenefit_unavailable'), + ("sponsors", "0022_sponsorcontact_administrative"), + ("sponsors", "0020_sponsorshipbenefit_unavailable"), ] - operations = [ - ] + operations = [] diff --git a/sponsors/migrations/0024_auto_20210414_1449.py b/sponsors/migrations/0024_auto_20210414_1449.py index bb463b39c..1dbaeefc3 100644 --- a/sponsors/migrations/0024_auto_20210414_1449.py +++ b/sponsors/migrations/0024_auto_20210414_1449.py @@ -4,14 +4,13 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0023_merge_20210406_1522'), + ("sponsors", "0023_merge_20210406_1522"), ] operations = [ migrations.RenameModel( - old_name='StatementOfWork', - new_name='Contract', + old_name="StatementOfWork", + new_name="Contract", ), ] diff --git a/sponsors/migrations/0025_auto_20210416_1939.py b/sponsors/migrations/0025_auto_20210416_1939.py index f289de131..c36072c56 100644 --- a/sponsors/migrations/0025_auto_20210416_1939.py +++ b/sponsors/migrations/0025_auto_20210416_1939.py @@ -5,44 +5,67 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0024_auto_20210414_1449'), + ("sponsors", "0024_auto_20210414_1449"), ] operations = [ migrations.AlterModelOptions( - name='contract', - options={'verbose_name': 'Contract', 'verbose_name_plural': 'Contracts'}, + name="contract", + options={"verbose_name": "Contract", "verbose_name_plural": "Contracts"}, ), migrations.AlterField( - model_name='contract', - name='sponsorship', - field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='contract', to='sponsors.Sponsorship'), + model_name="contract", + name="sponsorship", + field=models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="contract", + to="sponsors.Sponsorship", + ), ), migrations.AlterField( - model_name='legalclause', - name='clause', - field=models.TextField(help_text='Legal clause text to be added to contract', verbose_name='Clause'), + model_name="legalclause", + name="clause", + field=models.TextField(help_text="Legal clause text to be added to contract", verbose_name="Clause"), ), migrations.AlterField( - model_name='sponsorbenefit', - name='description', - field=models.TextField(blank=True, help_text='For display in the contract and sponsor dashboard.', null=True, verbose_name='Benefit Description'), + model_name="sponsorbenefit", + name="description", + field=models.TextField( + blank=True, + help_text="For display in the contract and sponsor dashboard.", + null=True, + verbose_name="Benefit Description", + ), ), migrations.AlterField( - model_name='sponsorbenefit', - name='name', - field=models.CharField(help_text='For display in the contract and sponsor dashboard.', max_length=1024, verbose_name='Benefit Name'), + model_name="sponsorbenefit", + name="name", + field=models.CharField( + help_text="For display in the contract and sponsor dashboard.", + max_length=1024, + verbose_name="Benefit Name", + ), ), migrations.AlterField( - model_name='sponsorshipbenefit', - name='legal_clauses', - field=models.ManyToManyField(blank=True, help_text='Legal clauses to be displayed in the contract', related_name='benefits', to='sponsors.LegalClause', verbose_name='Legal Clauses'), + model_name="sponsorshipbenefit", + name="legal_clauses", + field=models.ManyToManyField( + blank=True, + help_text="Legal clauses to be displayed in the contract", + related_name="benefits", + to="sponsors.LegalClause", + verbose_name="Legal Clauses", + ), ), migrations.AlterField( - model_name='sponsorshipbenefit', - name='name', - field=models.CharField(help_text='For display in the application form, contract, and sponsor dashboard.', max_length=1024, verbose_name='Benefit Name'), + model_name="sponsorshipbenefit", + name="name", + field=models.CharField( + help_text="For display in the application form, contract, and sponsor dashboard.", + max_length=1024, + verbose_name="Benefit Name", + ), ), ] diff --git a/sponsors/migrations/0026_auto_20210416_1940.py b/sponsors/migrations/0026_auto_20210416_1940.py index d9d170487..8baadc570 100644 --- a/sponsors/migrations/0026_auto_20210416_1940.py +++ b/sponsors/migrations/0026_auto_20210416_1940.py @@ -5,20 +5,19 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0025_auto_20210416_1939'), + ("sponsors", "0025_auto_20210416_1939"), ] operations = [ migrations.AlterField( - model_name='contract', - name='_legal_clauses_rendered', - field=models.TextField(default='', editable=False), + model_name="contract", + name="_legal_clauses_rendered", + field=models.TextField(default="", editable=False), ), migrations.AlterField( - model_name='contract', - name='legal_clauses', - field=markupfield.fields.MarkupField(blank=True, default='', rendered_field=True), + model_name="contract", + name="legal_clauses", + field=markupfield.fields.MarkupField(blank=True, default="", rendered_field=True), ), ] diff --git a/sponsors/migrations/0027_sponsorbenefit_program_name.py b/sponsors/migrations/0027_sponsorbenefit_program_name.py index 3271018b6..a57e8a551 100644 --- a/sponsors/migrations/0027_sponsorbenefit_program_name.py +++ b/sponsors/migrations/0027_sponsorbenefit_program_name.py @@ -4,16 +4,20 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0026_auto_20210416_1940'), + ("sponsors", "0026_auto_20210416_1940"), ] operations = [ migrations.AddField( - model_name='sponsorbenefit', - name='program_name', - field=models.CharField(default='Deleted Program', help_text='For display in the contract and sponsor dashboard.', max_length=1024, verbose_name='Program Name'), + model_name="sponsorbenefit", + name="program_name", + field=models.CharField( + default="Deleted Program", + help_text="For display in the contract and sponsor dashboard.", + max_length=1024, + verbose_name="Program Name", + ), preserve_default=False, ), ] diff --git a/sponsors/migrations/0028_auto_20210707_1426.py b/sponsors/migrations/0028_auto_20210707_1426.py index 861a75517..36b083244 100644 --- a/sponsors/migrations/0028_auto_20210707_1426.py +++ b/sponsors/migrations/0028_auto_20210707_1426.py @@ -5,15 +5,19 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0027_sponsorbenefit_program_name'), + ("sponsors", "0027_sponsorbenefit_program_name"), ] operations = [ migrations.AlterField( - model_name='sponsorshipbenefit', - name='program', - field=models.ForeignKey(help_text='Which sponsorship program the benefit is associated with.', on_delete=django.db.models.deletion.CASCADE, to='sponsors.SponsorshipProgram', verbose_name='Sponsorship Program'), + model_name="sponsorshipbenefit", + name="program", + field=models.ForeignKey( + help_text="Which sponsorship program the benefit is associated with.", + on_delete=django.db.models.deletion.CASCADE, + to="sponsors.SponsorshipProgram", + verbose_name="Sponsorship Program", + ), ), ] diff --git a/sponsors/migrations/0029_auto_20210715_2015.py b/sponsors/migrations/0029_auto_20210715_2015.py index fa973ac97..c4e3b0dd3 100644 --- a/sponsors/migrations/0029_auto_20210715_2015.py +++ b/sponsors/migrations/0029_auto_20210715_2015.py @@ -5,44 +5,84 @@ class Migration(migrations.Migration): - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('sponsors', '0028_auto_20210707_1426'), + ("contenttypes", "0002_remove_content_type_name"), + ("sponsors", "0028_auto_20210707_1426"), ] operations = [ migrations.CreateModel( - name='BenefitFeatureConfiguration', + name="BenefitFeatureConfiguration", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), ], options={ - 'verbose_name': 'Benefit Feature Configuration', - 'verbose_name_plural': 'Benefit Feature Configurations', + "verbose_name": "Benefit Feature Configuration", + "verbose_name_plural": "Benefit Feature Configurations", }, ), migrations.CreateModel( - name='LogoPlacementConfiguration', + name="LogoPlacementConfiguration", fields=[ - ('benefitfeatureconfiguration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.BenefitFeatureConfiguration')), - ('publisher', models.CharField(choices=[('psf', 'Foundation'), ('pycon', 'Pycon'), ('pypi', 'Pypi'), ('core', 'Core Dev')], help_text='On which site should the logo be displayed?', max_length=30, verbose_name='Publisher')), - ('logo_place', models.CharField(choices=[('sidebar', 'Sidebar'), ('sponsors', 'Sponsors Page'), ('jobs', 'Jobs'), ('blogpost', 'Blog'), ('footer', 'Footer'), ('docs', 'Docs'), ('download', 'Download Page'), ('devguide', 'Dev Guide')], help_text='Where the logo should be placed?', max_length=30, verbose_name='Logo Placement')), + ( + "benefitfeatureconfiguration_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.BenefitFeatureConfiguration", + ), + ), + ( + "publisher", + models.CharField( + choices=[("psf", "Foundation"), ("pycon", "Pycon"), ("pypi", "Pypi"), ("core", "Core Dev")], + help_text="On which site should the logo be displayed?", + max_length=30, + verbose_name="Publisher", + ), + ), + ( + "logo_place", + models.CharField( + choices=[ + ("sidebar", "Sidebar"), + ("sponsors", "Sponsors Page"), + ("jobs", "Jobs"), + ("blogpost", "Blog"), + ("footer", "Footer"), + ("docs", "Docs"), + ("download", "Download Page"), + ("devguide", "Dev Guide"), + ], + help_text="Where the logo should be placed?", + max_length=30, + verbose_name="Logo Placement", + ), + ), ], options={ - 'verbose_name': 'Logo Placement Configuration', - 'verbose_name_plural': 'Logo Placement Configurations', + "verbose_name": "Logo Placement Configuration", + "verbose_name_plural": "Logo Placement Configurations", }, - bases=('sponsors.benefitfeatureconfiguration',), + bases=("sponsors.benefitfeatureconfiguration",), ), migrations.AddField( - model_name='benefitfeatureconfiguration', - name='benefit', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sponsors.SponsorshipBenefit'), + model_name="benefitfeatureconfiguration", + name="benefit", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="sponsors.SponsorshipBenefit"), ), migrations.AddField( - model_name='benefitfeatureconfiguration', - name='polymorphic_ctype', - field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_sponsors.benefitfeatureconfiguration_set+', to='contenttypes.ContentType'), + model_name="benefitfeatureconfiguration", + name="polymorphic_ctype", + field=models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="polymorphic_sponsors.benefitfeatureconfiguration_set+", + to="contenttypes.ContentType", + ), ), ] diff --git a/sponsors/migrations/0030_auto_20210715_2023.py b/sponsors/migrations/0030_auto_20210715_2023.py index ac2b5fc56..fa4e96b54 100644 --- a/sponsors/migrations/0030_auto_20210715_2023.py +++ b/sponsors/migrations/0030_auto_20210715_2023.py @@ -5,50 +5,94 @@ class Migration(migrations.Migration): - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('sponsors', '0029_auto_20210715_2015'), + ("contenttypes", "0002_remove_content_type_name"), + ("sponsors", "0029_auto_20210715_2015"), ] operations = [ migrations.CreateModel( - name='BenefitFeature', + name="BenefitFeature", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), ], options={ - 'verbose_name': 'Benefit Feature', - 'verbose_name_plural': 'Benefit Features', + "verbose_name": "Benefit Feature", + "verbose_name_plural": "Benefit Features", }, ), migrations.AlterModelOptions( - name='logoplacementconfiguration', - options={'base_manager_name': 'objects', 'verbose_name': 'Logo Placement Configuration', 'verbose_name_plural': 'Logo Placement Configurations'}, + name="logoplacementconfiguration", + options={ + "base_manager_name": "objects", + "verbose_name": "Logo Placement Configuration", + "verbose_name_plural": "Logo Placement Configurations", + }, ), migrations.CreateModel( - name='LogoPlacement', + name="LogoPlacement", fields=[ - ('benefitfeature_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.BenefitFeature')), - ('publisher', models.CharField(choices=[('psf', 'Foundation'), ('pycon', 'Pycon'), ('pypi', 'Pypi'), ('core', 'Core Dev')], help_text='On which site should the logo be displayed?', max_length=30, verbose_name='Publisher')), - ('logo_place', models.CharField(choices=[('sidebar', 'Sidebar'), ('sponsors', 'Sponsors Page'), ('jobs', 'Jobs'), ('blogpost', 'Blog'), ('footer', 'Footer'), ('docs', 'Docs'), ('download', 'Download Page'), ('devguide', 'Dev Guide')], help_text='Where the logo should be placed?', max_length=30, verbose_name='Logo Placement')), + ( + "benefitfeature_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.BenefitFeature", + ), + ), + ( + "publisher", + models.CharField( + choices=[("psf", "Foundation"), ("pycon", "Pycon"), ("pypi", "Pypi"), ("core", "Core Dev")], + help_text="On which site should the logo be displayed?", + max_length=30, + verbose_name="Publisher", + ), + ), + ( + "logo_place", + models.CharField( + choices=[ + ("sidebar", "Sidebar"), + ("sponsors", "Sponsors Page"), + ("jobs", "Jobs"), + ("blogpost", "Blog"), + ("footer", "Footer"), + ("docs", "Docs"), + ("download", "Download Page"), + ("devguide", "Dev Guide"), + ], + help_text="Where the logo should be placed?", + max_length=30, + verbose_name="Logo Placement", + ), + ), ], options={ - 'verbose_name': 'Logo Placement', - 'verbose_name_plural': 'Logo Placement', - 'abstract': False, - 'base_manager_name': 'objects', + "verbose_name": "Logo Placement", + "verbose_name_plural": "Logo Placement", + "abstract": False, + "base_manager_name": "objects", }, - bases=('sponsors.benefitfeature', models.Model), + bases=("sponsors.benefitfeature", models.Model), ), migrations.AddField( - model_name='benefitfeature', - name='polymorphic_ctype', - field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_sponsors.benefitfeature_set+', to='contenttypes.ContentType'), + model_name="benefitfeature", + name="polymorphic_ctype", + field=models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="polymorphic_sponsors.benefitfeature_set+", + to="contenttypes.ContentType", + ), ), migrations.AddField( - model_name='benefitfeature', - name='sponsor_benefit', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sponsors.SponsorBenefit'), + model_name="benefitfeature", + name="sponsor_benefit", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="sponsors.SponsorBenefit"), ), ] diff --git a/sponsors/migrations/0031_auto_20210810_1232.py b/sponsors/migrations/0031_auto_20210810_1232.py index 30a93bb44..22e02a73a 100644 --- a/sponsors/migrations/0031_auto_20210810_1232.py +++ b/sponsors/migrations/0031_auto_20210810_1232.py @@ -4,20 +4,24 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0030_auto_20210715_2023'), + ("sponsors", "0030_auto_20210715_2023"), ] operations = [ migrations.AlterField( - model_name='sponsorcontact', - name='administrative', - field=models.BooleanField(default=False, help_text='Administrative contacts will only be notified regarding contracts.'), + model_name="sponsorcontact", + name="administrative", + field=models.BooleanField( + default=False, help_text="Administrative contacts will only be notified regarding contracts." + ), ), migrations.AlterField( - model_name='sponsorcontact', - name='primary', - field=models.BooleanField(default=False, help_text='The primary contact for a sponsorship will be responsible for managing deliverables we need to fulfill benefits. Primary contacts will receive all email notifications regarding sponsorship.'), + model_name="sponsorcontact", + name="primary", + field=models.BooleanField( + default=False, + help_text="The primary contact for a sponsorship will be responsible for managing deliverables we need to fulfill benefits. Primary contacts will receive all email notifications regarding sponsorship.", + ), ), ] diff --git a/sponsors/migrations/0032_sponsorcontact_accounting.py b/sponsors/migrations/0032_sponsorcontact_accounting.py index b450a7c74..af61765ab 100644 --- a/sponsors/migrations/0032_sponsorcontact_accounting.py +++ b/sponsors/migrations/0032_sponsorcontact_accounting.py @@ -4,15 +4,16 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0031_auto_20210810_1232'), + ("sponsors", "0031_auto_20210810_1232"), ] operations = [ migrations.AddField( - model_name='sponsorcontact', - name='accounting', - field=models.BooleanField(default=False, help_text='Accounting contacts will only be notified regarding invoices and payments.'), + model_name="sponsorcontact", + name="accounting", + field=models.BooleanField( + default=False, help_text="Accounting contacts will only be notified regarding invoices and payments." + ), ), ] diff --git a/sponsors/migrations/0033_tieredquantity_tieredquantityconfiguration.py b/sponsors/migrations/0033_tieredquantity_tieredquantityconfiguration.py index 63a596015..b3033916b 100644 --- a/sponsors/migrations/0033_tieredquantity_tieredquantityconfiguration.py +++ b/sponsors/migrations/0033_tieredquantity_tieredquantityconfiguration.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sponsors", "0032_sponsorcontact_accounting"), ] diff --git a/sponsors/migrations/0034_contract_document_docx.py b/sponsors/migrations/0034_contract_document_docx.py index ac89a27a0..816ea61e4 100644 --- a/sponsors/migrations/0034_contract_document_docx.py +++ b/sponsors/migrations/0034_contract_document_docx.py @@ -4,15 +4,16 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0033_tieredquantity_tieredquantityconfiguration'), + ("sponsors", "0033_tieredquantity_tieredquantityconfiguration"), ] operations = [ migrations.AddField( - model_name='contract', - name='document_docx', - field=models.FileField(blank=True, upload_to='sponsors/statmentes_of_work/docx/', verbose_name='Unsigned Docx'), + model_name="contract", + name="document_docx", + field=models.FileField( + blank=True, upload_to="sponsors/statmentes_of_work/docx/", verbose_name="Unsigned Docx" + ), ), ] diff --git a/sponsors/migrations/0035_auto_20210826_1929.py b/sponsors/migrations/0035_auto_20210826_1929.py index b6d22de8c..28d71b49f 100644 --- a/sponsors/migrations/0035_auto_20210826_1929.py +++ b/sponsors/migrations/0035_auto_20210826_1929.py @@ -5,25 +5,26 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0034_contract_document_docx'), + ("sponsors", "0034_contract_document_docx"), ] operations = [ migrations.AlterField( - model_name='contract', - name='document', - field=models.FileField(blank=True, upload_to='sponsors/contracts/', verbose_name='Unsigned PDF'), + model_name="contract", + name="document", + field=models.FileField(blank=True, upload_to="sponsors/contracts/", verbose_name="Unsigned PDF"), ), migrations.AlterField( - model_name='contract', - name='document_docx', - field=models.FileField(blank=True, upload_to='sponsors/contracts/docx/', verbose_name='Unsigned Docx'), + model_name="contract", + name="document_docx", + field=models.FileField(blank=True, upload_to="sponsors/contracts/docx/", verbose_name="Unsigned Docx"), ), migrations.AlterField( - model_name='contract', - name='signed_document', - field=models.FileField(blank=True, upload_to=sponsors.models.signed_contract_random_path, verbose_name='Signed PDF'), + model_name="contract", + name="signed_document", + field=models.FileField( + blank=True, upload_to=sponsors.models.signed_contract_random_path, verbose_name="Signed PDF" + ), ), ] diff --git a/sponsors/migrations/0036_auto_20210826_1930.py b/sponsors/migrations/0036_auto_20210826_1930.py index 58797d32a..80aa15af6 100644 --- a/sponsors/migrations/0036_auto_20210826_1930.py +++ b/sponsors/migrations/0036_auto_20210826_1930.py @@ -4,14 +4,13 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0035_auto_20210826_1929'), + ("sponsors", "0035_auto_20210826_1929"), ] operations = [ migrations.AlterModelOptions( - name='sponsorship', - options={'permissions': [('sponsor_publisher', 'Can access sponsor placement API')]}, + name="sponsorship", + options={"permissions": [("sponsor_publisher", "Can access sponsor placement API")]}, ), ] diff --git a/sponsors/migrations/0037_sponsorship_package.py b/sponsors/migrations/0037_sponsorship_package.py index 7d87c954d..8a8864574 100644 --- a/sponsors/migrations/0037_sponsorship_package.py +++ b/sponsors/migrations/0037_sponsorship_package.py @@ -5,15 +5,16 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0036_auto_20210826_1930'), + ("sponsors", "0036_auto_20210826_1930"), ] operations = [ migrations.AddField( - model_name='sponsorship', - name='package', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='sponsors.SponsorshipPackage'), + model_name="sponsorship", + name="package", + field=models.ForeignKey( + null=True, on_delete=django.db.models.deletion.SET_NULL, to="sponsors.SponsorshipPackage" + ), ), ] diff --git a/sponsors/migrations/0038_auto_20210827_1223.py b/sponsors/migrations/0038_auto_20210827_1223.py index 7e61331f1..770300b9b 100644 --- a/sponsors/migrations/0038_auto_20210827_1223.py +++ b/sponsors/migrations/0038_auto_20210827_1223.py @@ -4,8 +4,8 @@ def populate_sponsorship_package_fk(apps, schema_editor): - Sponsorship = apps.get_model('sponsors.Sponsorship') - SponsorshipPackage = apps.get_model('sponsors.SponsorshipPackage') + Sponsorship = apps.get_model("sponsors.Sponsorship") + SponsorshipPackage = apps.get_model("sponsors.SponsorshipPackage") for sponsorship in Sponsorship.objects.all().iterator(): try: @@ -17,11 +17,8 @@ def populate_sponsorship_package_fk(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0037_sponsorship_package'), + ("sponsors", "0037_sponsorship_package"), ] - operations = [ - migrations.RunPython(populate_sponsorship_package_fk, migrations.RunPython.noop) - ] + operations = [migrations.RunPython(populate_sponsorship_package_fk, migrations.RunPython.noop)] diff --git a/sponsors/migrations/0039_auto_20210827_1248.py b/sponsors/migrations/0039_auto_20210827_1248.py index 244542698..e09c5e1a5 100644 --- a/sponsors/migrations/0039_auto_20210827_1248.py +++ b/sponsors/migrations/0039_auto_20210827_1248.py @@ -4,15 +4,19 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0038_auto_20210827_1223'), + ("sponsors", "0038_auto_20210827_1223"), ] operations = [ migrations.AlterField( - model_name='sponsorship', - name='level_name', - field=models.CharField(blank=True, default='', help_text='DEPRECATED: will be removed after manual data sanity check.', max_length=64), + model_name="sponsorship", + name="level_name", + field=models.CharField( + blank=True, + default="", + help_text="DEPRECATED: will be removed after manual data sanity check.", + max_length=64, + ), ), ] diff --git a/sponsors/migrations/0040_auto_20210827_1313.py b/sponsors/migrations/0040_auto_20210827_1313.py index 5d62647fa..b8acc6f81 100644 --- a/sponsors/migrations/0040_auto_20210827_1313.py +++ b/sponsors/migrations/0040_auto_20210827_1313.py @@ -4,20 +4,24 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0039_auto_20210827_1248'), + ("sponsors", "0039_auto_20210827_1248"), ] operations = [ migrations.AddField( - model_name='sponsorshippackage', - name='logo_dimension', + model_name="sponsorshippackage", + name="logo_dimension", field=models.PositiveIntegerField(default=175), ), migrations.AlterField( - model_name='sponsorship', - name='level_name', - field=models.CharField(blank=True, default='', help_text='DEPRECATED: shall be removed after manual data sanity check.', max_length=64), + model_name="sponsorship", + name="level_name", + field=models.CharField( + blank=True, + default="", + help_text="DEPRECATED: shall be removed after manual data sanity check.", + max_length=64, + ), ), ] diff --git a/sponsors/migrations/0041_auto_20210827_1313.py b/sponsors/migrations/0041_auto_20210827_1313.py index b3822f1a9..a0b769504 100644 --- a/sponsors/migrations/0041_auto_20210827_1313.py +++ b/sponsors/migrations/0041_auto_20210827_1313.py @@ -26,11 +26,8 @@ def reset_logo_dimensions(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0040_auto_20210827_1313'), + ("sponsors", "0040_auto_20210827_1313"), ] - operations = [ - migrations.RunPython(populate_logo_dimensions, reset_logo_dimensions) - ] + operations = [migrations.RunPython(populate_logo_dimensions, reset_logo_dimensions)] diff --git a/sponsors/migrations/0042_auto_20210827_1318.py b/sponsors/migrations/0042_auto_20210827_1318.py index 628bb1a0c..2c888a1e2 100644 --- a/sponsors/migrations/0042_auto_20210827_1318.py +++ b/sponsors/migrations/0042_auto_20210827_1318.py @@ -4,15 +4,16 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0041_auto_20210827_1313'), + ("sponsors", "0041_auto_20210827_1313"), ] operations = [ migrations.AlterField( - model_name='sponsorshippackage', - name='logo_dimension', - field=models.PositiveIntegerField(blank=True, default=175, help_text='Internal value used to control logos dimensions at sponsors page'), + model_name="sponsorshippackage", + name="logo_dimension", + field=models.PositiveIntegerField( + blank=True, default=175, help_text="Internal value used to control logos dimensions at sponsors page" + ), ), ] diff --git a/sponsors/migrations/0043_auto_20210827_1343.py b/sponsors/migrations/0043_auto_20210827_1343.py index f0db0724e..3c234504a 100644 --- a/sponsors/migrations/0043_auto_20210827_1343.py +++ b/sponsors/migrations/0043_auto_20210827_1343.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0042_auto_20210827_1318'), + ("sponsors", "0042_auto_20210827_1318"), ] operations = [ migrations.RenameField( - model_name='sponsorship', - old_name='level_name', - new_name='level_name_old', + model_name="sponsorship", + old_name="level_name", + new_name="level_name_old", ), ] diff --git a/sponsors/migrations/0044_auto_20210827_1344.py b/sponsors/migrations/0044_auto_20210827_1344.py index 756ed6059..89f0f2a67 100644 --- a/sponsors/migrations/0044_auto_20210827_1344.py +++ b/sponsors/migrations/0044_auto_20210827_1344.py @@ -4,15 +4,20 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0043_auto_20210827_1343'), + ("sponsors", "0043_auto_20210827_1343"), ] operations = [ migrations.AlterField( - model_name='sponsorship', - name='level_name_old', - field=models.CharField(blank=True, default='', help_text='DEPRECATED: shall be removed after manual data sanity check.', max_length=64, verbose_name='Level name'), + model_name="sponsorship", + name="level_name_old", + field=models.CharField( + blank=True, + default="", + help_text="DEPRECATED: shall be removed after manual data sanity check.", + max_length=64, + verbose_name="Level name", + ), ), ] diff --git a/sponsors/migrations/0045_add_added_by_user_sponsorbenefit.py b/sponsors/migrations/0045_add_added_by_user_sponsorbenefit.py index 5dc04b9c0..f81e2ad96 100644 --- a/sponsors/migrations/0045_add_added_by_user_sponsorbenefit.py +++ b/sponsors/migrations/0045_add_added_by_user_sponsorbenefit.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0044_auto_20210827_1344'), + ("sponsors", "0044_auto_20210827_1344"), ] operations = [ migrations.AlterField( - model_name='sponsorbenefit', - name='added_by_user', - field=models.BooleanField(blank=True, default=False, verbose_name='Added by user?'), + model_name="sponsorbenefit", + name="added_by_user", + field=models.BooleanField(blank=True, default=False, verbose_name="Added by user?"), ), ] diff --git a/sponsors/migrations/0046_sponsorshippackage_advertise.py b/sponsors/migrations/0046_sponsorshippackage_advertise.py index 27b4e2ad8..c4a1f075a 100644 --- a/sponsors/migrations/0046_sponsorshippackage_advertise.py +++ b/sponsors/migrations/0046_sponsorshippackage_advertise.py @@ -4,15 +4,16 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0045_add_added_by_user_sponsorbenefit'), + ("sponsors", "0045_add_added_by_user_sponsorbenefit"), ] operations = [ migrations.AddField( - model_name='sponsorshippackage', - name='advertise', - field=models.BooleanField(default=False, help_text='If checked, this package will be advertised in the sponsosrhip application'), + model_name="sponsorshippackage", + name="advertise", + field=models.BooleanField( + default=False, help_text="If checked, this package will be advertised in the sponsosrhip application" + ), ), ] diff --git a/sponsors/migrations/0047_auto_20210908_1357.py b/sponsors/migrations/0047_auto_20210908_1357.py index f18d5c029..9b0dbaf67 100644 --- a/sponsors/migrations/0047_auto_20210908_1357.py +++ b/sponsors/migrations/0047_auto_20210908_1357.py @@ -10,11 +10,8 @@ def update_package_as_advertisable(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0046_sponsorshippackage_advertise'), + ("sponsors", "0046_sponsorshippackage_advertise"), ] - operations = [ - migrations.RunPython(update_package_as_advertisable, migrations.RunPython.noop) - ] + operations = [migrations.RunPython(update_package_as_advertisable, migrations.RunPython.noop)] diff --git a/sponsors/migrations/0048_auto_20210915_1425.py b/sponsors/migrations/0048_auto_20210915_1425.py index 4fc6ca7fc..24134fc12 100644 --- a/sponsors/migrations/0048_auto_20210915_1425.py +++ b/sponsors/migrations/0048_auto_20210915_1425.py @@ -4,15 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0047_auto_20210908_1357'), + ("sponsors", "0047_auto_20210908_1357"), ] operations = [ migrations.AlterField( - model_name='sponsorshippackage', - name='advertise', - field=models.BooleanField(blank=True, default=False, help_text='If checked, this package will be advertised in the sponsosrhip application'), + model_name="sponsorshippackage", + name="advertise", + field=models.BooleanField( + blank=True, + default=False, + help_text="If checked, this package will be advertised in the sponsosrhip application", + ), ), ] diff --git a/sponsors/migrations/0049_sponsoremailnotificationtemplate.py b/sponsors/migrations/0049_sponsoremailnotificationtemplate.py index bf71f7b96..f93bb66dd 100644 --- a/sponsors/migrations/0049_sponsoremailnotificationtemplate.py +++ b/sponsors/migrations/0049_sponsoremailnotificationtemplate.py @@ -4,25 +4,24 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0048_auto_20210915_1425'), + ("sponsors", "0048_auto_20210915_1425"), ] operations = [ migrations.CreateModel( - name='SponsorEmailNotificationTemplate', + name="SponsorEmailNotificationTemplate", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('internal_name', models.CharField(max_length=128)), - ('subject', models.CharField(max_length=128)), - ('content', models.TextField()), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("internal_name", models.CharField(max_length=128)), + ("subject", models.CharField(max_length=128)), + ("content", models.TextField()), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), ], options={ - 'verbose_name': 'Sponsor Email Notification Template', - 'verbose_name_plural': 'Sponsor Email Notification Templates', + "verbose_name": "Sponsor Email Notification Template", + "verbose_name_plural": "Sponsor Email Notification Templates", }, ), ] diff --git a/sponsors/migrations/0050_emailtargetable_emailtargetableconfiguration.py b/sponsors/migrations/0050_emailtargetable_emailtargetableconfiguration.py index 3f62c82ca..ccc746d71 100644 --- a/sponsors/migrations/0050_emailtargetable_emailtargetableconfiguration.py +++ b/sponsors/migrations/0050_emailtargetable_emailtargetableconfiguration.py @@ -5,36 +5,55 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0049_sponsoremailnotificationtemplate'), + ("sponsors", "0049_sponsoremailnotificationtemplate"), ] operations = [ migrations.CreateModel( - name='EmailTargetable', + name="EmailTargetable", fields=[ - ('benefitfeature_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.BenefitFeature')), + ( + "benefitfeature_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.BenefitFeature", + ), + ), ], options={ - 'verbose_name': 'Email Targetable Benefit', - 'verbose_name_plural': 'Email Targetable Benefits', - 'abstract': False, - 'base_manager_name': 'objects', + "verbose_name": "Email Targetable Benefit", + "verbose_name_plural": "Email Targetable Benefits", + "abstract": False, + "base_manager_name": "objects", }, - bases=('sponsors.benefitfeature', models.Model), + bases=("sponsors.benefitfeature", models.Model), ), migrations.CreateModel( - name='EmailTargetableConfiguration', + name="EmailTargetableConfiguration", fields=[ - ('benefitfeatureconfiguration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.BenefitFeatureConfiguration')), + ( + "benefitfeatureconfiguration_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.BenefitFeatureConfiguration", + ), + ), ], options={ - 'verbose_name': 'Email Targetable Configuration', - 'verbose_name_plural': 'Email Targetable Configurations', - 'abstract': False, - 'base_manager_name': 'objects', + "verbose_name": "Email Targetable Configuration", + "verbose_name_plural": "Email Targetable Configurations", + "abstract": False, + "base_manager_name": "objects", }, - bases=('sponsors.benefitfeatureconfiguration', models.Model), + bases=("sponsors.benefitfeatureconfiguration", models.Model), ), ] diff --git a/sponsors/migrations/0051_auto_20211022_1403.py b/sponsors/migrations/0051_auto_20211022_1403.py index 0ad5e0c8f..55302f67a 100644 --- a/sponsors/migrations/0051_auto_20211022_1403.py +++ b/sponsors/migrations/0051_auto_20211022_1403.py @@ -5,50 +5,71 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0050_emailtargetable_emailtargetableconfiguration'), + ("sponsors", "0050_emailtargetable_emailtargetableconfiguration"), ] operations = [ migrations.AlterField( - model_name='sponsor', - name='description', - field=models.TextField(help_text='Brief description of the sponsor for public display.', verbose_name='Description'), + model_name="sponsor", + name="description", + field=models.TextField( + help_text="Brief description of the sponsor for public display.", verbose_name="Description" + ), ), migrations.AlterField( - model_name='sponsor', - name='landing_page_url', - field=models.URLField(blank=True, help_text='Landing page URL. This may be provided by the sponsor, however the linked page may not contain any sales or marketing information.', null=True, verbose_name='Landing page URL'), + model_name="sponsor", + name="landing_page_url", + field=models.URLField( + blank=True, + help_text="Landing page URL. This may be provided by the sponsor, however the linked page may not contain any sales or marketing information.", + null=True, + verbose_name="Landing page URL", + ), ), migrations.AlterField( - model_name='sponsor', - name='name', - field=models.CharField(help_text='Name of the sponsor, for public display.', max_length=100, verbose_name='Name'), + model_name="sponsor", + name="name", + field=models.CharField( + help_text="Name of the sponsor, for public display.", max_length=100, verbose_name="Name" + ), ), migrations.AlterField( - model_name='sponsor', - name='primary_phone', - field=models.CharField(max_length=32, verbose_name='Primary Phone'), + model_name="sponsor", + name="primary_phone", + field=models.CharField(max_length=32, verbose_name="Primary Phone"), ), migrations.AlterField( - model_name='sponsor', - name='print_logo', - field=models.FileField(blank=True, help_text='For printed materials, signage, and projection. SVG or EPS', null=True, upload_to='sponsor_print_logos', verbose_name='Print logo'), + model_name="sponsor", + name="print_logo", + field=models.FileField( + blank=True, + help_text="For printed materials, signage, and projection. SVG or EPS", + null=True, + upload_to="sponsor_print_logos", + verbose_name="Print logo", + ), ), migrations.AlterField( - model_name='sponsor', - name='twitter_handle', - field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Twitter handle'), + model_name="sponsor", + name="twitter_handle", + field=models.CharField(blank=True, max_length=32, null=True, verbose_name="Twitter handle"), ), migrations.AlterField( - model_name='sponsor', - name='web_logo', - field=models.ImageField(help_text='For display on our sponsor webpage. High resolution PNG or JPG, smallest dimension no less than 256px', upload_to='sponsor_web_logos', verbose_name='Web logo'), + model_name="sponsor", + name="web_logo", + field=models.ImageField( + help_text="For display on our sponsor webpage. High resolution PNG or JPG, smallest dimension no less than 256px", + upload_to="sponsor_web_logos", + verbose_name="Web logo", + ), ), migrations.AlterField( - model_name='sponsorcontact', - name='primary', - field=models.BooleanField(default=False, help_text='The primary contact for a sponsorship will be responsible for managing deliverables we need to fulfill benefits. Primary contacts will receive all email notifications regarding sponsorship. '), + model_name="sponsorcontact", + name="primary", + field=models.BooleanField( + default=False, + help_text="The primary contact for a sponsorship will be responsible for managing deliverables we need to fulfill benefits. Primary contacts will receive all email notifications regarding sponsorship. ", + ), ), ] diff --git a/sponsors/migrations/0052_requiredimgasset_requiredimgassetconfiguration.py b/sponsors/migrations/0052_requiredimgasset_requiredimgassetconfiguration.py index c2946655f..bd231ed86 100644 --- a/sponsors/migrations/0052_requiredimgasset_requiredimgassetconfiguration.py +++ b/sponsors/migrations/0052_requiredimgasset_requiredimgassetconfiguration.py @@ -5,48 +5,101 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0051_auto_20211022_1403'), + ("sponsors", "0051_auto_20211022_1403"), ] operations = [ migrations.CreateModel( - name='RequiredImgAsset', + name="RequiredImgAsset", fields=[ - ('benefitfeature_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.BenefitFeature')), - ('related_to', models.CharField(choices=[('sponsor', 'Sponsor'), ('sponsorship', 'Sponsorship')], help_text='To which instance (Sponsor or Sponsorship) should this asset relate to.', max_length=30, verbose_name='Related To')), - ('internal_name', models.CharField(db_index=True, help_text='Unique name used internally to control if the sponsor/sponsorship already has the asset', max_length=128, unique=True, verbose_name='Internal Name')), - ('min_width', models.PositiveIntegerField()), - ('max_width', models.PositiveIntegerField()), - ('min_height', models.PositiveIntegerField()), - ('max_height', models.PositiveIntegerField()), + ( + "benefitfeature_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.BenefitFeature", + ), + ), + ( + "related_to", + models.CharField( + choices=[("sponsor", "Sponsor"), ("sponsorship", "Sponsorship")], + help_text="To which instance (Sponsor or Sponsorship) should this asset relate to.", + max_length=30, + verbose_name="Related To", + ), + ), + ( + "internal_name", + models.CharField( + db_index=True, + help_text="Unique name used internally to control if the sponsor/sponsorship already has the asset", + max_length=128, + unique=True, + verbose_name="Internal Name", + ), + ), + ("min_width", models.PositiveIntegerField()), + ("max_width", models.PositiveIntegerField()), + ("min_height", models.PositiveIntegerField()), + ("max_height", models.PositiveIntegerField()), ], options={ - 'verbose_name': 'Require Image Benefit', - 'verbose_name_plural': 'Require Image Benefits', - 'abstract': False, - 'base_manager_name': 'objects', + "verbose_name": "Require Image Benefit", + "verbose_name_plural": "Require Image Benefits", + "abstract": False, + "base_manager_name": "objects", }, - bases=('sponsors.benefitfeature', models.Model), + bases=("sponsors.benefitfeature", models.Model), ), migrations.CreateModel( - name='RequiredImgAssetConfiguration', + name="RequiredImgAssetConfiguration", fields=[ - ('benefitfeatureconfiguration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.BenefitFeatureConfiguration')), - ('related_to', models.CharField(choices=[('sponsor', 'Sponsor'), ('sponsorship', 'Sponsorship')], help_text='To which instance (Sponsor or Sponsorship) should this asset relate to.', max_length=30, verbose_name='Related To')), - ('internal_name', models.CharField(db_index=True, help_text='Unique name used internally to control if the sponsor/sponsorship already has the asset', max_length=128, unique=True, verbose_name='Internal Name')), - ('min_width', models.PositiveIntegerField()), - ('max_width', models.PositiveIntegerField()), - ('min_height', models.PositiveIntegerField()), - ('max_height', models.PositiveIntegerField()), + ( + "benefitfeatureconfiguration_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.BenefitFeatureConfiguration", + ), + ), + ( + "related_to", + models.CharField( + choices=[("sponsor", "Sponsor"), ("sponsorship", "Sponsorship")], + help_text="To which instance (Sponsor or Sponsorship) should this asset relate to.", + max_length=30, + verbose_name="Related To", + ), + ), + ( + "internal_name", + models.CharField( + db_index=True, + help_text="Unique name used internally to control if the sponsor/sponsorship already has the asset", + max_length=128, + unique=True, + verbose_name="Internal Name", + ), + ), + ("min_width", models.PositiveIntegerField()), + ("max_width", models.PositiveIntegerField()), + ("min_height", models.PositiveIntegerField()), + ("max_height", models.PositiveIntegerField()), ], options={ - 'verbose_name': 'Require Image Configuration', - 'verbose_name_plural': 'Require Image Configurations', - 'abstract': False, - 'base_manager_name': 'objects', + "verbose_name": "Require Image Configuration", + "verbose_name_plural": "Require Image Configurations", + "abstract": False, + "base_manager_name": "objects", }, - bases=('sponsors.benefitfeatureconfiguration', models.Model), + bases=("sponsors.benefitfeatureconfiguration", models.Model), ), ] diff --git a/sponsors/migrations/0053_genericasset_imgasset.py b/sponsors/migrations/0053_genericasset_imgasset.py index 616016c0f..31c483c5c 100644 --- a/sponsors/migrations/0053_genericasset_imgasset.py +++ b/sponsors/migrations/0053_genericasset_imgasset.py @@ -6,38 +6,59 @@ class Migration(migrations.Migration): - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('sponsors', '0052_requiredimgasset_requiredimgassetconfiguration'), + ("contenttypes", "0002_remove_content_type_name"), + ("sponsors", "0052_requiredimgasset_requiredimgassetconfiguration"), ] operations = [ migrations.CreateModel( - name='GenericAsset', + name="GenericAsset", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('object_id', models.PositiveIntegerField()), - ('internal_name', models.CharField(db_index=True, max_length=128, verbose_name='Internal Name')), - ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), - ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_sponsors.genericasset_set+', to='contenttypes.ContentType')), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("object_id", models.PositiveIntegerField()), + ("internal_name", models.CharField(db_index=True, max_length=128, verbose_name="Internal Name")), + ( + "content_type", + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="contenttypes.ContentType"), + ), + ( + "polymorphic_ctype", + models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="polymorphic_sponsors.genericasset_set+", + to="contenttypes.ContentType", + ), + ), ], options={ - 'verbose_name': 'Asset', - 'verbose_name_plural': 'Assets', - 'unique_together': {('content_type', 'object_id', 'internal_name')}, + "verbose_name": "Asset", + "verbose_name_plural": "Assets", + "unique_together": {("content_type", "object_id", "internal_name")}, }, ), migrations.CreateModel( - name='ImgAsset', + name="ImgAsset", fields=[ - ('genericasset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.GenericAsset')), - ('image', models.ImageField(null=True, upload_to=sponsors.models.assets.generic_asset_path)), + ( + "genericasset_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.GenericAsset", + ), + ), + ("image", models.ImageField(null=True, upload_to=sponsors.models.assets.generic_asset_path)), ], options={ - 'verbose_name': 'Image Asset', - 'verbose_name_plural': 'Image Assets', + "verbose_name": "Image Asset", + "verbose_name_plural": "Image Assets", }, - bases=('sponsors.genericasset',), + bases=("sponsors.genericasset",), ), ] diff --git a/sponsors/migrations/0054_auto_20211026_1432.py b/sponsors/migrations/0054_auto_20211026_1432.py index 1d0ecafc4..a6d43fa11 100644 --- a/sponsors/migrations/0054_auto_20211026_1432.py +++ b/sponsors/migrations/0054_auto_20211026_1432.py @@ -5,15 +5,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0053_genericasset_imgasset'), + ("sponsors", "0053_genericasset_imgasset"), ] operations = [ migrations.AddField( - model_name='genericasset', - name='uuid', + model_name="genericasset", + name="uuid", field=models.UUIDField(default=uuid.uuid4, editable=False, serialize=False), ), ] diff --git a/sponsors/migrations/0055_auto_20211026_1512.py b/sponsors/migrations/0055_auto_20211026_1512.py index 5bcf047e7..5998f4336 100644 --- a/sponsors/migrations/0055_auto_20211026_1512.py +++ b/sponsors/migrations/0055_auto_20211026_1512.py @@ -5,48 +5,131 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0054_auto_20211026_1432'), + ("sponsors", "0054_auto_20211026_1432"), ] operations = [ migrations.CreateModel( - name='RequiredTextAsset', + name="RequiredTextAsset", fields=[ - ('benefitfeature_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.BenefitFeature')), - ('related_to', models.CharField(choices=[('sponsor', 'Sponsor'), ('sponsorship', 'Sponsorship')], help_text='To which instance (Sponsor or Sponsorship) should this asset relate to.', max_length=30, verbose_name='Related To')), - ('internal_name', models.CharField(db_index=True, help_text='Unique name used internally to control if the sponsor/sponsorship already has the asset', max_length=128, unique=True, verbose_name='Internal Name')), - ('label', models.CharField(help_text="What's the title used to display the text input to the sponsor?", max_length=256)), - ('help_text', models.CharField(blank=True, default='', help_text='Any helper comment on how the input should be populated', max_length=256)), + ( + "benefitfeature_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.BenefitFeature", + ), + ), + ( + "related_to", + models.CharField( + choices=[("sponsor", "Sponsor"), ("sponsorship", "Sponsorship")], + help_text="To which instance (Sponsor or Sponsorship) should this asset relate to.", + max_length=30, + verbose_name="Related To", + ), + ), + ( + "internal_name", + models.CharField( + db_index=True, + help_text="Unique name used internally to control if the sponsor/sponsorship already has the asset", + max_length=128, + unique=True, + verbose_name="Internal Name", + ), + ), + ( + "label", + models.CharField( + help_text="What's the title used to display the text input to the sponsor?", max_length=256 + ), + ), + ( + "help_text", + models.CharField( + blank=True, + default="", + help_text="Any helper comment on how the input should be populated", + max_length=256, + ), + ), ], options={ - 'verbose_name': 'Require Text', - 'verbose_name_plural': 'Require Texts', - 'abstract': False, - 'base_manager_name': 'objects', + "verbose_name": "Require Text", + "verbose_name_plural": "Require Texts", + "abstract": False, + "base_manager_name": "objects", }, - bases=('sponsors.benefitfeature', models.Model), + bases=("sponsors.benefitfeature", models.Model), ), migrations.CreateModel( - name='RequiredTextAssetConfiguration', + name="RequiredTextAssetConfiguration", fields=[ - ('benefitfeatureconfiguration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.BenefitFeatureConfiguration')), - ('related_to', models.CharField(choices=[('sponsor', 'Sponsor'), ('sponsorship', 'Sponsorship')], help_text='To which instance (Sponsor or Sponsorship) should this asset relate to.', max_length=30, verbose_name='Related To')), - ('internal_name', models.CharField(db_index=True, help_text='Unique name used internally to control if the sponsor/sponsorship already has the asset', max_length=128, unique=True, verbose_name='Internal Name')), - ('label', models.CharField(help_text="What's the title used to display the text input to the sponsor?", max_length=256)), - ('help_text', models.CharField(blank=True, default='', help_text='Any helper comment on how the input should be populated', max_length=256)), + ( + "benefitfeatureconfiguration_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.BenefitFeatureConfiguration", + ), + ), + ( + "related_to", + models.CharField( + choices=[("sponsor", "Sponsor"), ("sponsorship", "Sponsorship")], + help_text="To which instance (Sponsor or Sponsorship) should this asset relate to.", + max_length=30, + verbose_name="Related To", + ), + ), + ( + "internal_name", + models.CharField( + db_index=True, + help_text="Unique name used internally to control if the sponsor/sponsorship already has the asset", + max_length=128, + unique=True, + verbose_name="Internal Name", + ), + ), + ( + "label", + models.CharField( + help_text="What's the title used to display the text input to the sponsor?", max_length=256 + ), + ), + ( + "help_text", + models.CharField( + blank=True, + default="", + help_text="Any helper comment on how the input should be populated", + max_length=256, + ), + ), ], options={ - 'verbose_name': 'Require Text Configuration', - 'verbose_name_plural': 'Require Text Configurations', - 'abstract': False, - 'base_manager_name': 'objects', + "verbose_name": "Require Text Configuration", + "verbose_name_plural": "Require Text Configurations", + "abstract": False, + "base_manager_name": "objects", }, - bases=('sponsors.benefitfeatureconfiguration', models.Model), + bases=("sponsors.benefitfeatureconfiguration", models.Model), ), migrations.AlterModelOptions( - name='requiredimgasset', - options={'base_manager_name': 'objects', 'verbose_name': 'Require Image', 'verbose_name_plural': 'Require Images'}, + name="requiredimgasset", + options={ + "base_manager_name": "objects", + "verbose_name": "Require Image", + "verbose_name_plural": "Require Images", + }, ), ] diff --git a/sponsors/migrations/0056_textasset.py b/sponsors/migrations/0056_textasset.py index 1fd7c68ba..6ec7f3300 100644 --- a/sponsors/migrations/0056_textasset.py +++ b/sponsors/migrations/0056_textasset.py @@ -5,22 +5,31 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0055_auto_20211026_1512'), + ("sponsors", "0055_auto_20211026_1512"), ] operations = [ migrations.CreateModel( - name='TextAsset', + name="TextAsset", fields=[ - ('genericasset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.GenericAsset')), - ('text', models.TextField(default='')), + ( + "genericasset_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.GenericAsset", + ), + ), + ("text", models.TextField(default="")), ], options={ - 'verbose_name': 'Image Asset', - 'verbose_name_plural': 'Image Assets', + "verbose_name": "Image Asset", + "verbose_name_plural": "Image Assets", }, - bases=('sponsors.genericasset',), + bases=("sponsors.genericasset",), ), ] diff --git a/sponsors/migrations/0057_auto_20211026_1529.py b/sponsors/migrations/0057_auto_20211026_1529.py index bd8ab17c7..8306b7326 100644 --- a/sponsors/migrations/0057_auto_20211026_1529.py +++ b/sponsors/migrations/0057_auto_20211026_1529.py @@ -5,15 +5,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0056_textasset'), + ("sponsors", "0056_textasset"), ] operations = [ migrations.AlterField( - model_name='genericasset', - name='uuid', + model_name="genericasset", + name="uuid", field=models.UUIDField(default=uuid.uuid4, editable=False), ), ] diff --git a/sponsors/migrations/0058_auto_20211029_1427.py b/sponsors/migrations/0058_auto_20211029_1427.py index af2b110d1..310fe5f2f 100644 --- a/sponsors/migrations/0058_auto_20211029_1427.py +++ b/sponsors/migrations/0058_auto_20211029_1427.py @@ -4,38 +4,57 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0057_auto_20211026_1529'), + ("sponsors", "0057_auto_20211026_1529"), ] operations = [ migrations.AlterField( - model_name='requiredimgasset', - name='internal_name', - field=models.CharField(db_index=True, help_text='Unique name used internally to control if the sponsor/sponsorship already has the asset', max_length=128, verbose_name='Internal Name'), + model_name="requiredimgasset", + name="internal_name", + field=models.CharField( + db_index=True, + help_text="Unique name used internally to control if the sponsor/sponsorship already has the asset", + max_length=128, + verbose_name="Internal Name", + ), ), migrations.AlterField( - model_name='requiredimgassetconfiguration', - name='internal_name', - field=models.CharField(db_index=True, help_text='Unique name used internally to control if the sponsor/sponsorship already has the asset', max_length=128, verbose_name='Internal Name'), + model_name="requiredimgassetconfiguration", + name="internal_name", + field=models.CharField( + db_index=True, + help_text="Unique name used internally to control if the sponsor/sponsorship already has the asset", + max_length=128, + verbose_name="Internal Name", + ), ), migrations.AlterField( - model_name='requiredtextasset', - name='internal_name', - field=models.CharField(db_index=True, help_text='Unique name used internally to control if the sponsor/sponsorship already has the asset', max_length=128, verbose_name='Internal Name'), + model_name="requiredtextasset", + name="internal_name", + field=models.CharField( + db_index=True, + help_text="Unique name used internally to control if the sponsor/sponsorship already has the asset", + max_length=128, + verbose_name="Internal Name", + ), ), migrations.AlterField( - model_name='requiredtextassetconfiguration', - name='internal_name', - field=models.CharField(db_index=True, help_text='Unique name used internally to control if the sponsor/sponsorship already has the asset', max_length=128, verbose_name='Internal Name'), + model_name="requiredtextassetconfiguration", + name="internal_name", + field=models.CharField( + db_index=True, + help_text="Unique name used internally to control if the sponsor/sponsorship already has the asset", + max_length=128, + verbose_name="Internal Name", + ), ), migrations.AddConstraint( - model_name='requiredimgassetconfiguration', - constraint=models.UniqueConstraint(fields=('internal_name',), name='uniq_img_asset_cfg'), + model_name="requiredimgassetconfiguration", + constraint=models.UniqueConstraint(fields=("internal_name",), name="uniq_img_asset_cfg"), ), migrations.AddConstraint( - model_name='requiredtextassetconfiguration', - constraint=models.UniqueConstraint(fields=('internal_name',), name='uniq_text_asset_cfg'), + model_name="requiredtextassetconfiguration", + constraint=models.UniqueConstraint(fields=("internal_name",), name="uniq_text_asset_cfg"), ), ] diff --git a/sponsors/migrations/0059_auto_20211029_1503.py b/sponsors/migrations/0059_auto_20211029_1503.py index db3c22fb9..271334584 100644 --- a/sponsors/migrations/0059_auto_20211029_1503.py +++ b/sponsors/migrations/0059_auto_20211029_1503.py @@ -4,14 +4,13 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0058_auto_20211029_1427'), + ("sponsors", "0058_auto_20211029_1427"), ] operations = [ migrations.AlterModelOptions( - name='textasset', - options={'verbose_name': 'Text Asset', 'verbose_name_plural': 'Text Assets'}, + name="textasset", + options={"verbose_name": "Text Asset", "verbose_name_plural": "Text Assets"}, ), ] diff --git a/sponsors/migrations/0060_auto_20211111_1526.py b/sponsors/migrations/0060_auto_20211111_1526.py index 2ceae7fe3..566f775c0 100644 --- a/sponsors/migrations/0060_auto_20211111_1526.py +++ b/sponsors/migrations/0060_auto_20211111_1526.py @@ -4,30 +4,29 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0059_auto_20211029_1503'), + ("sponsors", "0059_auto_20211029_1503"), ] operations = [ migrations.AddField( - model_name='logoplacement', - name='describe_as_sponsor', + model_name="logoplacement", + name="describe_as_sponsor", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='logoplacement', - name='link_to_sponsors_page', + model_name="logoplacement", + name="link_to_sponsors_page", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='logoplacementconfiguration', - name='describe_as_sponsor', + model_name="logoplacementconfiguration", + name="describe_as_sponsor", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='logoplacementconfiguration', - name='link_to_sponsors_page', + model_name="logoplacementconfiguration", + name="link_to_sponsors_page", field=models.BooleanField(default=False), ), ] diff --git a/sponsors/migrations/0061_auto_20211108_1419.py b/sponsors/migrations/0061_auto_20211108_1419.py index 1b41c476b..17c1f6833 100644 --- a/sponsors/migrations/0061_auto_20211108_1419.py +++ b/sponsors/migrations/0061_auto_20211108_1419.py @@ -4,42 +4,59 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0060_auto_20211111_1526'), + ("sponsors", "0060_auto_20211111_1526"), ] operations = [ migrations.AddField( - model_name='requiredimgasset', - name='help_text', - field=models.CharField(blank=True, default='', help_text='Any helper comment on how the input should be populated', max_length=256), + model_name="requiredimgasset", + name="help_text", + field=models.CharField( + blank=True, + default="", + help_text="Any helper comment on how the input should be populated", + max_length=256, + ), ), migrations.AddField( - model_name='requiredimgasset', - name='label', - field=models.CharField(default='label', help_text="What's the title used to display the input to the sponsor?", max_length=256), + model_name="requiredimgasset", + name="label", + field=models.CharField( + default="label", help_text="What's the title used to display the input to the sponsor?", max_length=256 + ), preserve_default=False, ), migrations.AddField( - model_name='requiredimgassetconfiguration', - name='help_text', - field=models.CharField(blank=True, default='', help_text='Any helper comment on how the input should be populated', max_length=256), + model_name="requiredimgassetconfiguration", + name="help_text", + field=models.CharField( + blank=True, + default="", + help_text="Any helper comment on how the input should be populated", + max_length=256, + ), ), migrations.AddField( - model_name='requiredimgassetconfiguration', - name='label', - field=models.CharField(default='label', help_text="What's the title used to display the input to the sponsor?", max_length=256), + model_name="requiredimgassetconfiguration", + name="label", + field=models.CharField( + default="label", help_text="What's the title used to display the input to the sponsor?", max_length=256 + ), preserve_default=False, ), migrations.AlterField( - model_name='requiredtextasset', - name='label', - field=models.CharField(help_text="What's the title used to display the input to the sponsor?", max_length=256), + model_name="requiredtextasset", + name="label", + field=models.CharField( + help_text="What's the title used to display the input to the sponsor?", max_length=256 + ), ), migrations.AlterField( - model_name='requiredtextassetconfiguration', - name='label', - field=models.CharField(help_text="What's the title used to display the input to the sponsor?", max_length=256), + model_name="requiredtextassetconfiguration", + name="label", + field=models.CharField( + help_text="What's the title used to display the input to the sponsor?", max_length=256 + ), ), ] diff --git a/sponsors/migrations/0062_auto_20211111_1529.py b/sponsors/migrations/0062_auto_20211111_1529.py index 962a893b9..cb1978abb 100644 --- a/sponsors/migrations/0062_auto_20211111_1529.py +++ b/sponsors/migrations/0062_auto_20211111_1529.py @@ -4,30 +4,39 @@ class Migration(migrations.Migration): - - dependencies = [ - ('sponsors', '0061_auto_20211108_1419') - ] + dependencies = [("sponsors", "0061_auto_20211108_1419")] operations = [ migrations.AlterField( - model_name='logoplacement', - name='describe_as_sponsor', - field=models.BooleanField(default=False, help_text='Override description with "SPONSOR_NAME is a SPONSOR_LEVEL sponsor of the Python Software Foundation".'), + model_name="logoplacement", + name="describe_as_sponsor", + field=models.BooleanField( + default=False, + help_text='Override description with "SPONSOR_NAME is a SPONSOR_LEVEL sponsor of the Python Software Foundation".', + ), ), migrations.AlterField( - model_name='logoplacement', - name='link_to_sponsors_page', - field=models.BooleanField(default=False, help_text='Override URL in placement to the PSF Sponsors Page, rather than the sponsor landing page url.'), + model_name="logoplacement", + name="link_to_sponsors_page", + field=models.BooleanField( + default=False, + help_text="Override URL in placement to the PSF Sponsors Page, rather than the sponsor landing page url.", + ), ), migrations.AlterField( - model_name='logoplacementconfiguration', - name='describe_as_sponsor', - field=models.BooleanField(default=False, help_text='Override description with "SPONSOR_NAME is a SPONSOR_LEVEL sponsor of the Python Software Foundation".'), + model_name="logoplacementconfiguration", + name="describe_as_sponsor", + field=models.BooleanField( + default=False, + help_text='Override description with "SPONSOR_NAME is a SPONSOR_LEVEL sponsor of the Python Software Foundation".', + ), ), migrations.AlterField( - model_name='logoplacementconfiguration', - name='link_to_sponsors_page', - field=models.BooleanField(default=False, help_text='Override URL in placement to the PSF Sponsors Page, rather than the sponsor landing page url.'), + model_name="logoplacementconfiguration", + name="link_to_sponsors_page", + field=models.BooleanField( + default=False, + help_text="Override URL in placement to the PSF Sponsors Page, rather than the sponsor landing page url.", + ), ), ] diff --git a/sponsors/migrations/0063_auto_20211220_1422.py b/sponsors/migrations/0063_auto_20211220_1422.py index a0164756c..57bf8d6e6 100644 --- a/sponsors/migrations/0063_auto_20211220_1422.py +++ b/sponsors/migrations/0063_auto_20211220_1422.py @@ -4,25 +4,32 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0062_auto_20211111_1529'), + ("sponsors", "0062_auto_20211111_1529"), ] operations = [ migrations.AddField( - model_name='sponsorshipbenefit', - name='a_la_carte', - field=models.BooleanField(default=False, help_text='À la carte benefits can be selected without the need of a package.', verbose_name='À La Carte'), + model_name="sponsorshipbenefit", + name="a_la_carte", + field=models.BooleanField( + default=False, + help_text="À la carte benefits can be selected without the need of a package.", + verbose_name="À La Carte", + ), ), migrations.AlterField( - model_name='requiredtextasset', - name='label', - field=models.CharField(help_text="What's the title used to display the text input to the sponsor?", max_length=256), + model_name="requiredtextasset", + name="label", + field=models.CharField( + help_text="What's the title used to display the text input to the sponsor?", max_length=256 + ), ), migrations.AlterField( - model_name='requiredtextassetconfiguration', - name='label', - field=models.CharField(help_text="What's the title used to display the text input to the sponsor?", max_length=256), + model_name="requiredtextassetconfiguration", + name="label", + field=models.CharField( + help_text="What's the title used to display the text input to the sponsor?", max_length=256 + ), ), ] diff --git a/sponsors/migrations/0064_sponsorshippackage_slug.py b/sponsors/migrations/0064_sponsorshippackage_slug.py index bf14023b4..699ef73dc 100644 --- a/sponsors/migrations/0064_sponsorshippackage_slug.py +++ b/sponsors/migrations/0064_sponsorshippackage_slug.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0063_auto_20211220_1422'), + ("sponsors", "0063_auto_20211220_1422"), ] operations = [ migrations.AddField( - model_name='sponsorshippackage', - name='slug', - field=models.SlugField(default='', help_text='Internal identifier used to reference this package.'), + model_name="sponsorshippackage", + name="slug", + field=models.SlugField(default="", help_text="Internal identifier used to reference this package."), ), ] diff --git a/sponsors/migrations/0065_auto_20211223_1309.py b/sponsors/migrations/0065_auto_20211223_1309.py index b6e900b4e..a33e18233 100644 --- a/sponsors/migrations/0065_auto_20211223_1309.py +++ b/sponsors/migrations/0065_auto_20211223_1309.py @@ -13,11 +13,8 @@ def populate_packages_slugs(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0064_sponsorshippackage_slug'), + ("sponsors", "0064_sponsorshippackage_slug"), ] - operations = [ - migrations.RunPython(populate_packages_slugs, migrations.RunPython.noop) - ] + operations = [migrations.RunPython(populate_packages_slugs, migrations.RunPython.noop)] diff --git a/sponsors/migrations/0066_auto_20211223_1318.py b/sponsors/migrations/0066_auto_20211223_1318.py index 6fb637f99..556847cc1 100644 --- a/sponsors/migrations/0066_auto_20211223_1318.py +++ b/sponsors/migrations/0066_auto_20211223_1318.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0065_auto_20211223_1309'), + ("sponsors", "0065_auto_20211223_1309"), ] operations = [ migrations.AlterField( - model_name='sponsorshippackage', - name='slug', - field=models.SlugField(help_text='Internal identifier used to reference this package.'), + model_name="sponsorshippackage", + name="slug", + field=models.SlugField(help_text="Internal identifier used to reference this package."), ), ] diff --git a/sponsors/migrations/0067_sponsorbenefit_a_la_carte.py b/sponsors/migrations/0067_sponsorbenefit_a_la_carte.py index cd3b98d4e..27b96672a 100644 --- a/sponsors/migrations/0067_sponsorbenefit_a_la_carte.py +++ b/sponsors/migrations/0067_sponsorbenefit_a_la_carte.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0066_auto_20211223_1318'), + ("sponsors", "0066_auto_20211223_1318"), ] operations = [ migrations.AddField( - model_name='sponsorbenefit', - name='a_la_carte', - field=models.BooleanField(blank=True, default=False, verbose_name='Added as a la carte benefit?'), + model_name="sponsorbenefit", + name="a_la_carte", + field=models.BooleanField(blank=True, default=False, verbose_name="Added as a la carte benefit?"), ), ] diff --git a/sponsors/migrations/0068_auto_20220110_1841.py b/sponsors/migrations/0068_auto_20220110_1841.py index 8149d57da..8dcbab32c 100644 --- a/sponsors/migrations/0068_auto_20220110_1841.py +++ b/sponsors/migrations/0068_auto_20220110_1841.py @@ -4,15 +4,17 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0067_sponsorbenefit_a_la_carte'), + ("sponsors", "0067_sponsorbenefit_a_la_carte"), ] operations = [ migrations.AlterField( - model_name='sponsorship', - name='for_modified_package', - field=models.BooleanField(default=False, help_text="If true, it means the user customized the package's benefits. Changes are listed under section 'User Customizations'."), + model_name="sponsorship", + name="for_modified_package", + field=models.BooleanField( + default=False, + help_text="If true, it means the user customized the package's benefits. Changes are listed under section 'User Customizations'.", + ), ), ] diff --git a/sponsors/migrations/0069_auto_20220110_2148.py b/sponsors/migrations/0069_auto_20220110_2148.py index 8492ce06b..23bbdf730 100644 --- a/sponsors/migrations/0069_auto_20220110_2148.py +++ b/sponsors/migrations/0069_auto_20220110_2148.py @@ -6,48 +6,129 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0068_auto_20220110_1841'), + ("sponsors", "0068_auto_20220110_1841"), ] operations = [ migrations.CreateModel( - name='ProvidedTextAsset', + name="ProvidedTextAsset", fields=[ - ('benefitfeature_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.BenefitFeature')), - ('related_to', models.CharField(choices=[('sponsor', 'Sponsor'), ('sponsorship', 'Sponsorship')], help_text='To which instance (Sponsor or Sponsorship) should this asset relate to.', max_length=30, verbose_name='Related To')), - ('internal_name', models.CharField(db_index=True, help_text='Unique name used internally to control if the sponsor/sponsorship already has the asset', max_length=128, verbose_name='Internal Name')), - ('label', models.CharField(help_text="What's the title used to display the text input to the sponsor?", max_length=256)), - ('help_text', models.CharField(blank=True, default='', help_text='Any helper comment on how the input should be populated', max_length=256)), + ( + "benefitfeature_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.BenefitFeature", + ), + ), + ( + "related_to", + models.CharField( + choices=[("sponsor", "Sponsor"), ("sponsorship", "Sponsorship")], + help_text="To which instance (Sponsor or Sponsorship) should this asset relate to.", + max_length=30, + verbose_name="Related To", + ), + ), + ( + "internal_name", + models.CharField( + db_index=True, + help_text="Unique name used internally to control if the sponsor/sponsorship already has the asset", + max_length=128, + verbose_name="Internal Name", + ), + ), + ( + "label", + models.CharField( + help_text="What's the title used to display the text input to the sponsor?", max_length=256 + ), + ), + ( + "help_text", + models.CharField( + blank=True, + default="", + help_text="Any helper comment on how the input should be populated", + max_length=256, + ), + ), ], options={ - 'verbose_name': 'Provided Text', - 'verbose_name_plural': 'Provided Texts', - 'abstract': False, - 'base_manager_name': 'objects', + "verbose_name": "Provided Text", + "verbose_name_plural": "Provided Texts", + "abstract": False, + "base_manager_name": "objects", }, - bases=(sponsors.models.benefits.ProvidedAssetMixin, 'sponsors.benefitfeature', models.Model), + bases=(sponsors.models.benefits.ProvidedAssetMixin, "sponsors.benefitfeature", models.Model), ), migrations.CreateModel( - name='ProvidedTextAssetConfiguration', + name="ProvidedTextAssetConfiguration", fields=[ - ('benefitfeatureconfiguration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.BenefitFeatureConfiguration')), - ('related_to', models.CharField(choices=[('sponsor', 'Sponsor'), ('sponsorship', 'Sponsorship')], help_text='To which instance (Sponsor or Sponsorship) should this asset relate to.', max_length=30, verbose_name='Related To')), - ('internal_name', models.CharField(db_index=True, help_text='Unique name used internally to control if the sponsor/sponsorship already has the asset', max_length=128, verbose_name='Internal Name')), - ('label', models.CharField(help_text="What's the title used to display the text input to the sponsor?", max_length=256)), - ('help_text', models.CharField(blank=True, default='', help_text='Any helper comment on how the input should be populated', max_length=256)), + ( + "benefitfeatureconfiguration_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.BenefitFeatureConfiguration", + ), + ), + ( + "related_to", + models.CharField( + choices=[("sponsor", "Sponsor"), ("sponsorship", "Sponsorship")], + help_text="To which instance (Sponsor or Sponsorship) should this asset relate to.", + max_length=30, + verbose_name="Related To", + ), + ), + ( + "internal_name", + models.CharField( + db_index=True, + help_text="Unique name used internally to control if the sponsor/sponsorship already has the asset", + max_length=128, + verbose_name="Internal Name", + ), + ), + ( + "label", + models.CharField( + help_text="What's the title used to display the text input to the sponsor?", max_length=256 + ), + ), + ( + "help_text", + models.CharField( + blank=True, + default="", + help_text="Any helper comment on how the input should be populated", + max_length=256, + ), + ), ], options={ - 'verbose_name': 'Provided Text Configuration', - 'verbose_name_plural': 'Provided Text Configurations', - 'abstract': False, - 'base_manager_name': 'objects', + "verbose_name": "Provided Text Configuration", + "verbose_name_plural": "Provided Text Configurations", + "abstract": False, + "base_manager_name": "objects", }, - bases=(sponsors.models.benefits.AssetConfigurationMixin, 'sponsors.benefitfeatureconfiguration', models.Model), + bases=( + sponsors.models.benefits.AssetConfigurationMixin, + "sponsors.benefitfeatureconfiguration", + models.Model, + ), ), migrations.AddConstraint( - model_name='providedtextassetconfiguration', - constraint=models.UniqueConstraint(fields=('internal_name',), name='uniq_provided_text_asset_cfg'), + model_name="providedtextassetconfiguration", + constraint=models.UniqueConstraint(fields=("internal_name",), name="uniq_provided_text_asset_cfg"), ), ] diff --git a/sponsors/migrations/0070_auto_20220111_2055.py b/sponsors/migrations/0070_auto_20220111_2055.py index 94f8075cb..22f3ef42f 100644 --- a/sponsors/migrations/0070_auto_20220111_2055.py +++ b/sponsors/migrations/0070_auto_20220111_2055.py @@ -7,74 +7,165 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0069_auto_20220110_2148'), + ("sponsors", "0069_auto_20220110_2148"), ] operations = [ migrations.CreateModel( - name='FileAsset', + name="FileAsset", fields=[ - ('genericasset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.GenericAsset')), - ('file', models.FileField(null=True, upload_to=sponsors.models.assets.generic_asset_path)), + ( + "genericasset_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.GenericAsset", + ), + ), + ("file", models.FileField(null=True, upload_to=sponsors.models.assets.generic_asset_path)), ], options={ - 'verbose_name': 'File Asset', - 'verbose_name_plural': 'File Assets', + "verbose_name": "File Asset", + "verbose_name_plural": "File Assets", }, - bases=('sponsors.genericasset',), + bases=("sponsors.genericasset",), ), migrations.CreateModel( - name='ProvidedFileAsset', + name="ProvidedFileAsset", fields=[ - ('benefitfeature_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.BenefitFeature')), - ('related_to', models.CharField(choices=[('sponsor', 'Sponsor'), ('sponsorship', 'Sponsorship')], help_text='To which instance (Sponsor or Sponsorship) should this asset relate to.', max_length=30, verbose_name='Related To')), - ('internal_name', models.CharField(db_index=True, help_text='Unique name used internally to control if the sponsor/sponsorship already has the asset', max_length=128, verbose_name='Internal Name')), - ('shared', models.BooleanField(default=False)), - ('label', models.CharField(help_text="What's the title used to display the file to the sponsor?", max_length=256)), - ('help_text', models.CharField(blank=True, default='', help_text='Any helper comment on how the file should be used', max_length=256)), - ('shared_file', models.FileField(blank=True, null=True, upload_to='')), + ( + "benefitfeature_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.BenefitFeature", + ), + ), + ( + "related_to", + models.CharField( + choices=[("sponsor", "Sponsor"), ("sponsorship", "Sponsorship")], + help_text="To which instance (Sponsor or Sponsorship) should this asset relate to.", + max_length=30, + verbose_name="Related To", + ), + ), + ( + "internal_name", + models.CharField( + db_index=True, + help_text="Unique name used internally to control if the sponsor/sponsorship already has the asset", + max_length=128, + verbose_name="Internal Name", + ), + ), + ("shared", models.BooleanField(default=False)), + ( + "label", + models.CharField( + help_text="What's the title used to display the file to the sponsor?", max_length=256 + ), + ), + ( + "help_text", + models.CharField( + blank=True, + default="", + help_text="Any helper comment on how the file should be used", + max_length=256, + ), + ), + ("shared_file", models.FileField(blank=True, null=True, upload_to="")), ], options={ - 'verbose_name': 'Provided File', - 'verbose_name_plural': 'Provided Files', - 'abstract': False, - 'base_manager_name': 'objects', + "verbose_name": "Provided File", + "verbose_name_plural": "Provided Files", + "abstract": False, + "base_manager_name": "objects", }, - bases=(sponsors.models.benefits.ProvidedAssetMixin, 'sponsors.benefitfeature', models.Model), + bases=(sponsors.models.benefits.ProvidedAssetMixin, "sponsors.benefitfeature", models.Model), ), migrations.CreateModel( - name='ProvidedFileAssetConfiguration', + name="ProvidedFileAssetConfiguration", fields=[ - ('benefitfeatureconfiguration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.BenefitFeatureConfiguration')), - ('related_to', models.CharField(choices=[('sponsor', 'Sponsor'), ('sponsorship', 'Sponsorship')], help_text='To which instance (Sponsor or Sponsorship) should this asset relate to.', max_length=30, verbose_name='Related To')), - ('internal_name', models.CharField(db_index=True, help_text='Unique name used internally to control if the sponsor/sponsorship already has the asset', max_length=128, verbose_name='Internal Name')), - ('shared', models.BooleanField(default=False)), - ('label', models.CharField(help_text="What's the title used to display the file to the sponsor?", max_length=256)), - ('help_text', models.CharField(blank=True, default='', help_text='Any helper comment on how the file should be used', max_length=256)), - ('shared_file', models.FileField(blank=True, null=True, upload_to='')), + ( + "benefitfeatureconfiguration_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.BenefitFeatureConfiguration", + ), + ), + ( + "related_to", + models.CharField( + choices=[("sponsor", "Sponsor"), ("sponsorship", "Sponsorship")], + help_text="To which instance (Sponsor or Sponsorship) should this asset relate to.", + max_length=30, + verbose_name="Related To", + ), + ), + ( + "internal_name", + models.CharField( + db_index=True, + help_text="Unique name used internally to control if the sponsor/sponsorship already has the asset", + max_length=128, + verbose_name="Internal Name", + ), + ), + ("shared", models.BooleanField(default=False)), + ( + "label", + models.CharField( + help_text="What's the title used to display the file to the sponsor?", max_length=256 + ), + ), + ( + "help_text", + models.CharField( + blank=True, + default="", + help_text="Any helper comment on how the file should be used", + max_length=256, + ), + ), + ("shared_file", models.FileField(blank=True, null=True, upload_to="")), ], options={ - 'verbose_name': 'Provided File Configuration', - 'verbose_name_plural': 'Provided File Configurations', - 'abstract': False, - 'base_manager_name': 'objects', + "verbose_name": "Provided File Configuration", + "verbose_name_plural": "Provided File Configurations", + "abstract": False, + "base_manager_name": "objects", }, - bases=(sponsors.models.benefits.AssetConfigurationMixin, 'sponsors.benefitfeatureconfiguration', models.Model), + bases=( + sponsors.models.benefits.AssetConfigurationMixin, + "sponsors.benefitfeatureconfiguration", + models.Model, + ), ), migrations.AddField( - model_name='providedtextasset', - name='shared', + model_name="providedtextasset", + name="shared", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='providedtextassetconfiguration', - name='shared', + model_name="providedtextassetconfiguration", + name="shared", field=models.BooleanField(default=False), ), migrations.AddConstraint( - model_name='providedfileassetconfiguration', - constraint=models.UniqueConstraint(fields=('internal_name',), name='uniq_provided_file_asset_cfg'), + model_name="providedfileassetconfiguration", + constraint=models.UniqueConstraint(fields=("internal_name",), name="uniq_provided_file_asset_cfg"), ), ] diff --git a/sponsors/migrations/0071_auto_20220113_1843.py b/sponsors/migrations/0071_auto_20220113_1843.py index 7c66e5ba5..b91112660 100644 --- a/sponsors/migrations/0071_auto_20220113_1843.py +++ b/sponsors/migrations/0071_auto_20220113_1843.py @@ -4,30 +4,29 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0070_auto_20220111_2055'), + ("sponsors", "0070_auto_20220111_2055"), ] operations = [ migrations.AddField( - model_name='requiredimgasset', - name='due_date', + model_name="requiredimgasset", + name="due_date", field=models.DateField(blank=True, default=None, null=True), ), migrations.AddField( - model_name='requiredimgassetconfiguration', - name='due_date', + model_name="requiredimgassetconfiguration", + name="due_date", field=models.DateField(blank=True, default=None, null=True), ), migrations.AddField( - model_name='requiredtextasset', - name='due_date', + model_name="requiredtextasset", + name="due_date", field=models.DateField(blank=True, default=None, null=True), ), migrations.AddField( - model_name='requiredtextassetconfiguration', - name='due_date', + model_name="requiredtextassetconfiguration", + name="due_date", field=models.DateField(blank=True, default=None, null=True), ), ] diff --git a/sponsors/migrations/0072_auto_20220125_2005.py b/sponsors/migrations/0072_auto_20220125_2005.py index 86247bc08..3d9d224d4 100644 --- a/sponsors/migrations/0072_auto_20220125_2005.py +++ b/sponsors/migrations/0072_auto_20220125_2005.py @@ -4,20 +4,23 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0071_auto_20220113_1843'), + ("sponsors", "0071_auto_20220113_1843"), ] operations = [ migrations.AddField( - model_name='requiredtextasset', - name='max_length', - field=models.IntegerField(blank=True, default=None, help_text='Limit to length of the input, empty means unlimited', null=True), + model_name="requiredtextasset", + name="max_length", + field=models.IntegerField( + blank=True, default=None, help_text="Limit to length of the input, empty means unlimited", null=True + ), ), migrations.AddField( - model_name='requiredtextassetconfiguration', - name='max_length', - field=models.IntegerField(blank=True, default=None, help_text='Limit to length of the input, empty means unlimited', null=True), + model_name="requiredtextassetconfiguration", + name="max_length", + field=models.IntegerField( + blank=True, default=None, help_text="Limit to length of the input, empty means unlimited", null=True + ), ), ] diff --git a/sponsors/migrations/0073_auto_20220128_1906.py b/sponsors/migrations/0073_auto_20220128_1906.py index 39abe43b8..bde81bc01 100644 --- a/sponsors/migrations/0073_auto_20220128_1906.py +++ b/sponsors/migrations/0073_auto_20220128_1906.py @@ -6,62 +6,153 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0072_auto_20220125_2005'), + ("sponsors", "0072_auto_20220125_2005"), ] operations = [ migrations.CreateModel( - name='RequiredResponseAsset', + name="RequiredResponseAsset", fields=[ - ('benefitfeature_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.BenefitFeature')), - ('related_to', models.CharField(choices=[('sponsor', 'Sponsor'), ('sponsorship', 'Sponsorship')], help_text='To which instance (Sponsor or Sponsorship) should this asset relate to.', max_length=30, verbose_name='Related To')), - ('internal_name', models.CharField(db_index=True, help_text='Unique name used internally to control if the sponsor/sponsorship already has the asset', max_length=128, verbose_name='Internal Name')), - ('label', models.CharField(help_text="What's the title used to display the input to the sponsor?", max_length=256)), - ('help_text', models.CharField(blank=True, default='', help_text='Any helper comment on how the input should be populated', max_length=256)), - ('due_date', models.DateField(blank=True, default=None, null=True)), + ( + "benefitfeature_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.BenefitFeature", + ), + ), + ( + "related_to", + models.CharField( + choices=[("sponsor", "Sponsor"), ("sponsorship", "Sponsorship")], + help_text="To which instance (Sponsor or Sponsorship) should this asset relate to.", + max_length=30, + verbose_name="Related To", + ), + ), + ( + "internal_name", + models.CharField( + db_index=True, + help_text="Unique name used internally to control if the sponsor/sponsorship already has the asset", + max_length=128, + verbose_name="Internal Name", + ), + ), + ( + "label", + models.CharField( + help_text="What's the title used to display the input to the sponsor?", max_length=256 + ), + ), + ( + "help_text", + models.CharField( + blank=True, + default="", + help_text="Any helper comment on how the input should be populated", + max_length=256, + ), + ), + ("due_date", models.DateField(blank=True, default=None, null=True)), ], options={ - 'verbose_name': 'Require Response', - 'verbose_name_plural': 'Required Responses', - 'abstract': False, - 'base_manager_name': 'objects', + "verbose_name": "Require Response", + "verbose_name_plural": "Required Responses", + "abstract": False, + "base_manager_name": "objects", }, - bases=(sponsors.models.benefits.RequiredAssetMixin, 'sponsors.benefitfeature', models.Model), + bases=(sponsors.models.benefits.RequiredAssetMixin, "sponsors.benefitfeature", models.Model), ), migrations.CreateModel( - name='RequiredResponseAssetConfiguration', + name="RequiredResponseAssetConfiguration", fields=[ - ('benefitfeatureconfiguration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.BenefitFeatureConfiguration')), - ('related_to', models.CharField(choices=[('sponsor', 'Sponsor'), ('sponsorship', 'Sponsorship')], help_text='To which instance (Sponsor or Sponsorship) should this asset relate to.', max_length=30, verbose_name='Related To')), - ('internal_name', models.CharField(db_index=True, help_text='Unique name used internally to control if the sponsor/sponsorship already has the asset', max_length=128, verbose_name='Internal Name')), - ('label', models.CharField(help_text="What's the title used to display the input to the sponsor?", max_length=256)), - ('help_text', models.CharField(blank=True, default='', help_text='Any helper comment on how the input should be populated', max_length=256)), - ('due_date', models.DateField(blank=True, default=None, null=True)), + ( + "benefitfeatureconfiguration_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.BenefitFeatureConfiguration", + ), + ), + ( + "related_to", + models.CharField( + choices=[("sponsor", "Sponsor"), ("sponsorship", "Sponsorship")], + help_text="To which instance (Sponsor or Sponsorship) should this asset relate to.", + max_length=30, + verbose_name="Related To", + ), + ), + ( + "internal_name", + models.CharField( + db_index=True, + help_text="Unique name used internally to control if the sponsor/sponsorship already has the asset", + max_length=128, + verbose_name="Internal Name", + ), + ), + ( + "label", + models.CharField( + help_text="What's the title used to display the input to the sponsor?", max_length=256 + ), + ), + ( + "help_text", + models.CharField( + blank=True, + default="", + help_text="Any helper comment on how the input should be populated", + max_length=256, + ), + ), + ("due_date", models.DateField(blank=True, default=None, null=True)), ], options={ - 'verbose_name': 'Require Response Configuration', - 'verbose_name_plural': 'Require Response Configurations', - 'abstract': False, - 'base_manager_name': 'objects', + "verbose_name": "Require Response Configuration", + "verbose_name_plural": "Require Response Configurations", + "abstract": False, + "base_manager_name": "objects", }, - bases=(sponsors.models.benefits.AssetConfigurationMixin, 'sponsors.benefitfeatureconfiguration', models.Model), + bases=( + sponsors.models.benefits.AssetConfigurationMixin, + "sponsors.benefitfeatureconfiguration", + models.Model, + ), ), migrations.CreateModel( - name='ResponseAsset', + name="ResponseAsset", fields=[ - ('genericasset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sponsors.GenericAsset')), - ('response', models.CharField(choices=[('YES', 'Yes'), ('NO', 'No')], max_length=32, null=True)), + ( + "genericasset_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="sponsors.GenericAsset", + ), + ), + ("response", models.CharField(choices=[("YES", "Yes"), ("NO", "No")], max_length=32, null=True)), ], options={ - 'verbose_name': 'Response Asset', - 'verbose_name_plural': 'Response Assets', + "verbose_name": "Response Asset", + "verbose_name_plural": "Response Assets", }, - bases=('sponsors.genericasset',), + bases=("sponsors.genericasset",), ), migrations.AddConstraint( - model_name='requiredresponseassetconfiguration', - constraint=models.UniqueConstraint(fields=('internal_name',), name='uniq_response_asset_cfg'), + model_name="requiredresponseassetconfiguration", + constraint=models.UniqueConstraint(fields=("internal_name",), name="uniq_response_asset_cfg"), ), ] diff --git a/sponsors/migrations/0074_auto_20220211_1659.py b/sponsors/migrations/0074_auto_20220211_1659.py index 2ed9083c9..98930412c 100644 --- a/sponsors/migrations/0074_auto_20220211_1659.py +++ b/sponsors/migrations/0074_auto_20220211_1659.py @@ -5,20 +5,19 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0073_auto_20220128_1906'), + ("sponsors", "0073_auto_20220128_1906"), ] operations = [ migrations.AddField( - model_name='providedtextasset', - name='shared_text', + model_name="providedtextasset", + name="shared_text", field=models.TextField(blank=True, null=True), ), migrations.AddField( - model_name='providedtextassetconfiguration', - name='shared_text', + model_name="providedtextassetconfiguration", + name="shared_text", field=models.TextField(blank=True, null=True), ), ] diff --git a/sponsors/migrations/0075_auto_20220303_2023.py b/sponsors/migrations/0075_auto_20220303_2023.py index 134e34fad..fe79ba6f5 100644 --- a/sponsors/migrations/0075_auto_20220303_2023.py +++ b/sponsors/migrations/0075_auto_20220303_2023.py @@ -6,15 +6,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0074_auto_20220211_1659'), + ("sponsors", "0074_auto_20220211_1659"), ] operations = [ migrations.AddField( - model_name='sponsorship', - name='overlapped_by', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='sponsors.Sponsorship'), + model_name="sponsorship", + name="overlapped_by", + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to="sponsors.Sponsorship"), ), ] diff --git a/sponsors/migrations/0076_auto_20220728_1550.py b/sponsors/migrations/0076_auto_20220728_1550.py index 7c0abb0fd..dcc58d225 100644 --- a/sponsors/migrations/0076_auto_20220728_1550.py +++ b/sponsors/migrations/0076_auto_20220728_1550.py @@ -5,60 +5,63 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0075_auto_20220303_2023'), + ("sponsors", "0075_auto_20220303_2023"), ] operations = [ migrations.AlterModelOptions( - name='benefitfeature', - options={'base_manager_name': 'non_polymorphic', 'verbose_name': 'Benefit Feature', 'verbose_name_plural': 'Benefit Features'}, + name="benefitfeature", + options={ + "base_manager_name": "non_polymorphic", + "verbose_name": "Benefit Feature", + "verbose_name_plural": "Benefit Features", + }, ), migrations.AlterModelOptions( - name='genericasset', - options={'base_manager_name': 'non_polymorphic', 'verbose_name': 'Asset', 'verbose_name_plural': 'Assets'}, + name="genericasset", + options={"base_manager_name": "non_polymorphic", "verbose_name": "Asset", "verbose_name_plural": "Assets"}, ), migrations.AlterModelManagers( - name='benefitfeature', + name="benefitfeature", managers=[ - ('objects', django.db.models.manager.Manager()), - ('non_polymorphic', django.db.models.manager.Manager()), + ("objects", django.db.models.manager.Manager()), + ("non_polymorphic", django.db.models.manager.Manager()), ], ), migrations.AlterModelManagers( - name='fileasset', + name="fileasset", managers=[ - ('objects', django.db.models.manager.Manager()), - ('non_polymorphic', django.db.models.manager.Manager()), + ("objects", django.db.models.manager.Manager()), + ("non_polymorphic", django.db.models.manager.Manager()), ], ), migrations.AlterModelManagers( - name='genericasset', + name="genericasset", managers=[ - ('objects', django.db.models.manager.Manager()), - ('non_polymorphic', django.db.models.manager.Manager()), + ("objects", django.db.models.manager.Manager()), + ("non_polymorphic", django.db.models.manager.Manager()), ], ), migrations.AlterModelManagers( - name='imgasset', + name="imgasset", managers=[ - ('objects', django.db.models.manager.Manager()), - ('non_polymorphic', django.db.models.manager.Manager()), + ("objects", django.db.models.manager.Manager()), + ("non_polymorphic", django.db.models.manager.Manager()), ], ), migrations.AlterModelManagers( - name='responseasset', + name="responseasset", managers=[ - ('objects', django.db.models.manager.Manager()), - ('non_polymorphic', django.db.models.manager.Manager()), + ("objects", django.db.models.manager.Manager()), + ("non_polymorphic", django.db.models.manager.Manager()), ], ), migrations.AlterModelManagers( - name='textasset', + name="textasset", managers=[ - ('objects', django.db.models.manager.Manager()), - ('non_polymorphic', django.db.models.manager.Manager()), + ("objects", django.db.models.manager.Manager()), + ("non_polymorphic", django.db.models.manager.Manager()), ], ), ] diff --git a/sponsors/migrations/0077_sponsorshipcurrentyear.py b/sponsors/migrations/0077_sponsorshipcurrentyear.py index b99721f42..f063ad5e6 100644 --- a/sponsors/migrations/0077_sponsorshipcurrentyear.py +++ b/sponsors/migrations/0077_sponsorshipcurrentyear.py @@ -5,17 +5,28 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0076_auto_20220728_1550'), + ("sponsors", "0076_auto_20220728_1550"), ] operations = [ migrations.CreateModel( - name='SponsorshipCurrentYear', + name="SponsorshipCurrentYear", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('year', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(limit_value=2022, message='The min year value is 2022.'), django.core.validators.MaxValueValidator(limit_value=2050, message='The max year value is 2050.')])), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "year", + models.PositiveIntegerField( + validators=[ + django.core.validators.MinValueValidator( + limit_value=2022, message="The min year value is 2022." + ), + django.core.validators.MaxValueValidator( + limit_value=2050, message="The max year value is 2050." + ), + ] + ), + ), ], ), ] diff --git a/sponsors/migrations/0078_init_current_year_singleton.py b/sponsors/migrations/0078_init_current_year_singleton.py index dc12554f7..ce74013ad 100644 --- a/sponsors/migrations/0078_init_current_year_singleton.py +++ b/sponsors/migrations/0078_init_current_year_singleton.py @@ -9,11 +9,8 @@ def populate_singleton(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0077_sponsorshipcurrentyear'), + ("sponsors", "0077_sponsorshipcurrentyear"), ] - operations = [ - migrations.RunPython(populate_singleton, migrations.RunPython.noop) - ] + operations = [migrations.RunPython(populate_singleton, migrations.RunPython.noop)] diff --git a/sponsors/migrations/0079_index_to_force_singleton.py b/sponsors/migrations/0079_index_to_force_singleton.py index 3472950c0..7cf2d7935 100644 --- a/sponsors/migrations/0079_index_to_force_singleton.py +++ b/sponsors/migrations/0079_index_to_force_singleton.py @@ -20,12 +20,9 @@ class Migration(migrations.Migration): atomic = False dependencies = [ - ('sponsors', '0078_init_current_year_singleton'), + ("sponsors", "0078_init_current_year_singleton"), ] operations = [ - migrations.RunSQL( - sql=CREATE_SINGLETON_INDEX, - reverse_sql=DROP_SINGLETON_INDEX - ), + migrations.RunSQL(sql=CREATE_SINGLETON_INDEX, reverse_sql=DROP_SINGLETON_INDEX), ] diff --git a/sponsors/migrations/0080_auto_20220728_1644.py b/sponsors/migrations/0080_auto_20220728_1644.py index c099c2aea..bae8af62e 100644 --- a/sponsors/migrations/0080_auto_20220728_1644.py +++ b/sponsors/migrations/0080_auto_20220728_1644.py @@ -5,19 +5,24 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0079_index_to_force_singleton'), + ("sponsors", "0079_index_to_force_singleton"), ] operations = [ migrations.AlterModelOptions( - name='sponsorshipcurrentyear', - options={'verbose_name': 'Active Year', 'verbose_name_plural': 'Active Year'}, + name="sponsorshipcurrentyear", + options={"verbose_name": "Active Year", "verbose_name_plural": "Active Year"}, ), migrations.AlterField( - model_name='sponsorshipcurrentyear', - name='year', - field=models.PositiveIntegerField(help_text='Every new sponsorship application will be considered as an application from to the active year.', validators=[django.core.validators.MinValueValidator(limit_value=2022, message='The min year value is 2022.'), django.core.validators.MaxValueValidator(limit_value=2050, message='The max year value is 2050.')]), + model_name="sponsorshipcurrentyear", + name="year", + field=models.PositiveIntegerField( + help_text="Every new sponsorship application will be considered as an application from to the active year.", + validators=[ + django.core.validators.MinValueValidator(limit_value=2022, message="The min year value is 2022."), + django.core.validators.MaxValueValidator(limit_value=2050, message="The max year value is 2050."), + ], + ), ), ] diff --git a/sponsors/migrations/0081_sponsorship_application_year.py b/sponsors/migrations/0081_sponsorship_application_year.py index 0dcbe05bf..c079e2c5c 100644 --- a/sponsors/migrations/0081_sponsorship_application_year.py +++ b/sponsors/migrations/0081_sponsorship_application_year.py @@ -5,15 +5,20 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0080_auto_20220728_1644'), + ("sponsors", "0080_auto_20220728_1644"), ] operations = [ migrations.AddField( - model_name='sponsorship', - name='application_year', - field=models.PositiveIntegerField(null=True, validators=[django.core.validators.MinValueValidator(limit_value=2022, message='The min year value is 2022.'), django.core.validators.MaxValueValidator(limit_value=2050, message='The max year value is 2050.')]), + model_name="sponsorship", + name="application_year", + field=models.PositiveIntegerField( + null=True, + validators=[ + django.core.validators.MinValueValidator(limit_value=2022, message="The min year value is 2022."), + django.core.validators.MaxValueValidator(limit_value=2050, message="The max year value is 2050."), + ], + ), ), ] diff --git a/sponsors/migrations/0082_auto_20220729_1613.py b/sponsors/migrations/0082_auto_20220729_1613.py index 116b4a011..a2621c415 100644 --- a/sponsors/migrations/0082_auto_20220729_1613.py +++ b/sponsors/migrations/0082_auto_20220729_1613.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0081_sponsorship_application_year'), + ("sponsors", "0081_sponsorship_application_year"), ] operations = [ migrations.RenameField( - model_name='sponsorship', - old_name='application_year', - new_name='year', + model_name="sponsorship", + old_name="application_year", + new_name="year", ), ] diff --git a/sponsors/migrations/0083_auto_20220729_1624.py b/sponsors/migrations/0083_auto_20220729_1624.py index ff6b2ab85..aaff57855 100644 --- a/sponsors/migrations/0083_auto_20220729_1624.py +++ b/sponsors/migrations/0083_auto_20220729_1624.py @@ -5,20 +5,31 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0082_auto_20220729_1613'), + ("sponsors", "0082_auto_20220729_1613"), ] operations = [ migrations.AddField( - model_name='sponsorshipbenefit', - name='year', - field=models.PositiveIntegerField(null=True, validators=[django.core.validators.MinValueValidator(limit_value=2022, message='The min year value is 2022.'), django.core.validators.MaxValueValidator(limit_value=2050, message='The max year value is 2050.')]), + model_name="sponsorshipbenefit", + name="year", + field=models.PositiveIntegerField( + null=True, + validators=[ + django.core.validators.MinValueValidator(limit_value=2022, message="The min year value is 2022."), + django.core.validators.MaxValueValidator(limit_value=2050, message="The max year value is 2050."), + ], + ), ), migrations.AddField( - model_name='sponsorshippackage', - name='year', - field=models.PositiveIntegerField(null=True, validators=[django.core.validators.MinValueValidator(limit_value=2022, message='The min year value is 2022.'), django.core.validators.MaxValueValidator(limit_value=2050, message='The max year value is 2050.')]), + model_name="sponsorshippackage", + name="year", + field=models.PositiveIntegerField( + null=True, + validators=[ + django.core.validators.MinValueValidator(limit_value=2022, message="The min year value is 2022."), + django.core.validators.MaxValueValidator(limit_value=2050, message="The max year value is 2050."), + ], + ), ), ] diff --git a/sponsors/migrations/0084_init_configured_objs_year.py b/sponsors/migrations/0084_init_configured_objs_year.py index 75b761d72..3fddd7bcc 100644 --- a/sponsors/migrations/0084_init_configured_objs_year.py +++ b/sponsors/migrations/0084_init_configured_objs_year.py @@ -2,6 +2,7 @@ from django.db import migrations + def populate_with_current_year(apps, schema_editor): SponsorshipPackage = apps.get_model("sponsors", "SponsorshipPackage") SponsorshipBenefit = apps.get_model("sponsors", "SponsorshipBenefit") @@ -21,13 +22,9 @@ def reset_current_year(apps, schema_editor): SponsorshipBenefit.objects.all().update(year=None) - class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0083_auto_20220729_1624'), + ("sponsors", "0083_auto_20220729_1624"), ] - operations = [ - migrations.RunPython(populate_with_current_year, reset_current_year) - ] + operations = [migrations.RunPython(populate_with_current_year, reset_current_year)] diff --git a/sponsors/migrations/0085_auto_20220730_0945.py b/sponsors/migrations/0085_auto_20220730_0945.py index ad86168c4..34ef40d4b 100644 --- a/sponsors/migrations/0085_auto_20220730_0945.py +++ b/sponsors/migrations/0085_auto_20220730_0945.py @@ -5,25 +5,45 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0084_init_configured_objs_year'), + ("sponsors", "0084_init_configured_objs_year"), ] operations = [ migrations.AlterField( - model_name='sponsorship', - name='year', - field=models.PositiveIntegerField(db_index=True, null=True, validators=[django.core.validators.MinValueValidator(limit_value=2022, message='The min year value is 2022.'), django.core.validators.MaxValueValidator(limit_value=2050, message='The max year value is 2050.')]), + model_name="sponsorship", + name="year", + field=models.PositiveIntegerField( + db_index=True, + null=True, + validators=[ + django.core.validators.MinValueValidator(limit_value=2022, message="The min year value is 2022."), + django.core.validators.MaxValueValidator(limit_value=2050, message="The max year value is 2050."), + ], + ), ), migrations.AlterField( - model_name='sponsorshipbenefit', - name='year', - field=models.PositiveIntegerField(db_index=True, null=True, validators=[django.core.validators.MinValueValidator(limit_value=2022, message='The min year value is 2022.'), django.core.validators.MaxValueValidator(limit_value=2050, message='The max year value is 2050.')]), + model_name="sponsorshipbenefit", + name="year", + field=models.PositiveIntegerField( + db_index=True, + null=True, + validators=[ + django.core.validators.MinValueValidator(limit_value=2022, message="The min year value is 2022."), + django.core.validators.MaxValueValidator(limit_value=2050, message="The max year value is 2050."), + ], + ), ), migrations.AlterField( - model_name='sponsorshippackage', - name='year', - field=models.PositiveIntegerField(db_index=True, null=True, validators=[django.core.validators.MinValueValidator(limit_value=2022, message='The min year value is 2022.'), django.core.validators.MaxValueValidator(limit_value=2050, message='The max year value is 2050.')]), + model_name="sponsorshippackage", + name="year", + field=models.PositiveIntegerField( + db_index=True, + null=True, + validators=[ + django.core.validators.MinValueValidator(limit_value=2022, message="The min year value is 2022."), + django.core.validators.MaxValueValidator(limit_value=2050, message="The max year value is 2050."), + ], + ), ), ] diff --git a/sponsors/migrations/0086_auto_20220809_1655.py b/sponsors/migrations/0086_auto_20220809_1655.py index e7d8bda65..75ad8897a 100644 --- a/sponsors/migrations/0086_auto_20220809_1655.py +++ b/sponsors/migrations/0086_auto_20220809_1655.py @@ -4,14 +4,13 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0085_auto_20220730_0945'), + ("sponsors", "0085_auto_20220730_0945"), ] operations = [ migrations.AlterModelOptions( - name='sponsorshippackage', - options={'ordering': ('-year', 'order')}, + name="sponsorshippackage", + options={"ordering": ("-year", "order")}, ), ] diff --git a/sponsors/migrations/0087_auto_20220810_1647.py b/sponsors/migrations/0087_auto_20220810_1647.py index 41043bf4f..ea47ba5ba 100644 --- a/sponsors/migrations/0087_auto_20220810_1647.py +++ b/sponsors/migrations/0087_auto_20220810_1647.py @@ -4,23 +4,26 @@ class Migration(migrations.Migration): - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('sponsors', '0086_auto_20220809_1655'), + ("contenttypes", "0002_remove_content_type_name"), + ("sponsors", "0086_auto_20220809_1655"), ] operations = [ migrations.RenameModel( - old_name='TieredQuantity', - new_name='TieredBenefit', + old_name="TieredQuantity", + new_name="TieredBenefit", ), migrations.RenameModel( - old_name='TieredQuantityConfiguration', - new_name='TieredBenefitConfiguration', + old_name="TieredQuantityConfiguration", + new_name="TieredBenefitConfiguration", ), migrations.AlterModelOptions( - name='tieredbenefit', - options={'base_manager_name': 'objects', 'verbose_name': 'Tiered Benefit', 'verbose_name_plural': 'Tiered Benefits'}, + name="tieredbenefit", + options={ + "base_manager_name": "objects", + "verbose_name": "Tiered Benefit", + "verbose_name_plural": "Tiered Benefits", + }, ), ] diff --git a/sponsors/migrations/0088_auto_20220810_1655.py b/sponsors/migrations/0088_auto_20220810_1655.py index f0203331b..bbc366c22 100644 --- a/sponsors/migrations/0088_auto_20220810_1655.py +++ b/sponsors/migrations/0088_auto_20220810_1655.py @@ -4,20 +4,29 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0087_auto_20220810_1647'), + ("sponsors", "0087_auto_20220810_1647"), ] operations = [ migrations.AddField( - model_name='tieredbenefit', - name='display_label', - field=models.CharField(blank=True, default='', help_text='If populated, this will be displayed instead of the quantity value.', max_length=32), + model_name="tieredbenefit", + name="display_label", + field=models.CharField( + blank=True, + default="", + help_text="If populated, this will be displayed instead of the quantity value.", + max_length=32, + ), ), migrations.AddField( - model_name='tieredbenefitconfiguration', - name='display_label', - field=models.CharField(blank=True, default='', help_text='If populated, this will be displayed instead of the quantity value.', max_length=32), + model_name="tieredbenefitconfiguration", + name="display_label", + field=models.CharField( + blank=True, + default="", + help_text="If populated, this will be displayed instead of the quantity value.", + max_length=32, + ), ), ] diff --git a/sponsors/migrations/0089_auto_20220812_1312.py b/sponsors/migrations/0089_auto_20220812_1312.py index 5bd61374e..549f60f30 100644 --- a/sponsors/migrations/0089_auto_20220812_1312.py +++ b/sponsors/migrations/0089_auto_20220812_1312.py @@ -4,20 +4,19 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0088_auto_20220810_1655'), + ("sponsors", "0088_auto_20220810_1655"), ] operations = [ migrations.RenameField( - model_name='sponsorbenefit', - old_name='a_la_carte', - new_name='standalone', + model_name="sponsorbenefit", + old_name="a_la_carte", + new_name="standalone", ), migrations.RenameField( - model_name='sponsorshipbenefit', - old_name='a_la_carte', - new_name='standalone', + model_name="sponsorshipbenefit", + old_name="a_la_carte", + new_name="standalone", ), ] diff --git a/sponsors/migrations/0090_auto_20220812_1314.py b/sponsors/migrations/0090_auto_20220812_1314.py index ccae36bb9..3b4a81e32 100644 --- a/sponsors/migrations/0090_auto_20220812_1314.py +++ b/sponsors/migrations/0090_auto_20220812_1314.py @@ -4,20 +4,23 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0089_auto_20220812_1312'), + ("sponsors", "0089_auto_20220812_1312"), ] operations = [ migrations.AlterField( - model_name='sponsorbenefit', - name='standalone', - field=models.BooleanField(blank=True, default=False, verbose_name='Added as standalone benefit?'), + model_name="sponsorbenefit", + name="standalone", + field=models.BooleanField(blank=True, default=False, verbose_name="Added as standalone benefit?"), ), migrations.AlterField( - model_name='sponsorshipbenefit', - name='standalone', - field=models.BooleanField(default=False, help_text='Standalone benefits can be selected without the need of a package.', verbose_name='Standalone'), + model_name="sponsorshipbenefit", + name="standalone", + field=models.BooleanField( + default=False, + help_text="Standalone benefits can be selected without the need of a package.", + verbose_name="Standalone", + ), ), ] diff --git a/sponsors/migrations/0091_sponsorshippackage_allow_a_la_carte.py b/sponsors/migrations/0091_sponsorshippackage_allow_a_la_carte.py index a4023d1cd..0cf73a247 100644 --- a/sponsors/migrations/0091_sponsorshippackage_allow_a_la_carte.py +++ b/sponsors/migrations/0091_sponsorshippackage_allow_a_la_carte.py @@ -4,15 +4,16 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0090_auto_20220812_1314'), + ("sponsors", "0090_auto_20220812_1314"), ] operations = [ migrations.AddField( - model_name='sponsorshippackage', - name='allow_a_la_carte', - field=models.BooleanField(default=True, help_text='If disabled, a la carte benefits will be disabled in application form'), + model_name="sponsorshippackage", + name="allow_a_la_carte", + field=models.BooleanField( + default=True, help_text="If disabled, a la carte benefits will be disabled in application form" + ), ), ] diff --git a/sponsors/migrations/0092_auto_20220816_1517.py b/sponsors/migrations/0092_auto_20220816_1517.py index 7f74eb14f..9e7b2d233 100644 --- a/sponsors/migrations/0092_auto_20220816_1517.py +++ b/sponsors/migrations/0092_auto_20220816_1517.py @@ -4,15 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0091_sponsorshippackage_allow_a_la_carte'), + ("sponsors", "0091_sponsorshippackage_allow_a_la_carte"), ] operations = [ migrations.AlterField( - model_name='sponsorshipbenefit', - name='unavailable', - field=models.BooleanField(default=False, help_text='If selected, this benefit will not be visible or available to applicants.', verbose_name='Benefit is unavailable'), + model_name="sponsorshipbenefit", + name="unavailable", + field=models.BooleanField( + default=False, + help_text="If selected, this benefit will not be visible or available to applicants.", + verbose_name="Benefit is unavailable", + ), ), ] diff --git a/sponsors/migrations/0093_auto_20230214_2113.py b/sponsors/migrations/0093_auto_20230214_2113.py index 853d14606..b88ec0ddd 100644 --- a/sponsors/migrations/0093_auto_20230214_2113.py +++ b/sponsors/migrations/0093_auto_20230214_2113.py @@ -4,15 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0092_auto_20220816_1517'), + ("sponsors", "0092_auto_20220816_1517"), ] operations = [ migrations.AlterField( - model_name='sponsorshipbenefit', - name='package_only', - field=models.BooleanField(default=False, help_text='If a benefit is only available via a sponsorship package and not as an add-on, select this option.', verbose_name='Sponsor Package Only Benefit'), + model_name="sponsorshipbenefit", + name="package_only", + field=models.BooleanField( + default=False, + help_text="If a benefit is only available via a sponsorship package and not as an add-on, select this option.", + verbose_name="Sponsor Package Only Benefit", + ), ), ] diff --git a/sponsors/migrations/0094_sponsorship_locked.py b/sponsors/migrations/0094_sponsorship_locked.py index c1c6a8152..436773cd6 100644 --- a/sponsors/migrations/0094_sponsorship_locked.py +++ b/sponsors/migrations/0094_sponsorship_locked.py @@ -4,29 +4,30 @@ from sponsors.models.sponsorship import Sponsorship as _Sponsorship + def forwards_func(apps, schema_editor): - Sponsorship = apps.get_model('sponsors', 'Sponsorship') + Sponsorship = apps.get_model("sponsors", "Sponsorship") db_alias = schema_editor.connection.alias for sponsorship in Sponsorship.objects.all(): sponsorship.locked = not (sponsorship.status == _Sponsorship.APPLIED) sponsorship.save() + def reverse_func(apps, schema_editor): pass class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0093_auto_20230214_2113'), + ("sponsors", "0093_auto_20230214_2113"), ] operations = [ migrations.AddField( - model_name='sponsorship', - name='locked', + model_name="sponsorship", + name="locked", field=models.BooleanField(default=False), ), - migrations.RunPython(forwards_func, reverse_func) + migrations.RunPython(forwards_func, reverse_func), ] diff --git a/sponsors/migrations/0096_auto_20231214_2108.py b/sponsors/migrations/0096_auto_20231214_2108.py index 11c6dde5b..f4a060ef4 100644 --- a/sponsors/migrations/0096_auto_20231214_2108.py +++ b/sponsors/migrations/0096_auto_20231214_2108.py @@ -5,57 +5,48 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0095_auto_20231214_2025'), + ("sponsors", "0095_auto_20231214_2025"), ] operations = [ migrations.AlterModelManagers( - name='benefitfeatureconfiguration', + name="benefitfeatureconfiguration", managers=[ - ('objects', django.db.models.manager.Manager()), - ('non_polymorphic', django.db.models.manager.Manager()), + ("objects", django.db.models.manager.Manager()), + ("non_polymorphic", django.db.models.manager.Manager()), ], ), migrations.AlterModelManagers( - name='emailtargetableconfiguration', - managers=[ - ], + name="emailtargetableconfiguration", + managers=[], ), migrations.AlterModelManagers( - name='logoplacementconfiguration', - managers=[ - ], + name="logoplacementconfiguration", + managers=[], ), migrations.AlterModelManagers( - name='providedfileassetconfiguration', - managers=[ - ], + name="providedfileassetconfiguration", + managers=[], ), migrations.AlterModelManagers( - name='providedtextassetconfiguration', - managers=[ - ], + name="providedtextassetconfiguration", + managers=[], ), migrations.AlterModelManagers( - name='requiredimgassetconfiguration', - managers=[ - ], + name="requiredimgassetconfiguration", + managers=[], ), migrations.AlterModelManagers( - name='requiredresponseassetconfiguration', - managers=[ - ], + name="requiredresponseassetconfiguration", + managers=[], ), migrations.AlterModelManagers( - name='requiredtextassetconfiguration', - managers=[ - ], + name="requiredtextassetconfiguration", + managers=[], ), migrations.AlterModelManagers( - name='tieredbenefitconfiguration', - managers=[ - ], + name="tieredbenefitconfiguration", + managers=[], ), ] diff --git a/sponsors/migrations/0097_sponsorship_renewal.py b/sponsors/migrations/0097_sponsorship_renewal.py index fdbc347b3..d180e1a47 100644 --- a/sponsors/migrations/0097_sponsorship_renewal.py +++ b/sponsors/migrations/0097_sponsorship_renewal.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0096_auto_20231214_2108'), + ("sponsors", "0096_auto_20231214_2108"), ] operations = [ migrations.AddField( - model_name='sponsorship', - name='renewal', + model_name="sponsorship", + name="renewal", field=models.BooleanField(blank=True, null=True), ), ] diff --git a/sponsors/migrations/0098_auto_20231219_1910.py b/sponsors/migrations/0098_auto_20231219_1910.py index 3c466bb75..510971d81 100644 --- a/sponsors/migrations/0098_auto_20231219_1910.py +++ b/sponsors/migrations/0098_auto_20231219_1910.py @@ -4,15 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0097_sponsorship_renewal'), + ("sponsors", "0097_sponsorship_renewal"), ] operations = [ migrations.AlterField( - model_name='sponsorship', - name='renewal', - field=models.BooleanField(blank=True, help_text='If true, it means the sponsorship is a renewal of a previous sponsorship and will use the renewal template for contracting.', null=True), + model_name="sponsorship", + name="renewal", + field=models.BooleanField( + blank=True, + help_text="If true, it means the sponsorship is a renewal of a previous sponsorship and will use the renewal template for contracting.", + null=True, + ), ), ] diff --git a/sponsors/migrations/0099_auto_20231224_1854.py b/sponsors/migrations/0099_auto_20231224_1854.py index d8aaa436c..eb0f7d51d 100644 --- a/sponsors/migrations/0099_auto_20231224_1854.py +++ b/sponsors/migrations/0099_auto_20231224_1854.py @@ -5,15 +5,21 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0098_auto_20231219_1910'), + ("sponsors", "0098_auto_20231219_1910"), ] operations = [ migrations.AlterField( - model_name='sponsor', - name='print_logo', - field=models.FileField(blank=True, help_text='For printed materials, signage, and projection. SVG or EPS', null=True, upload_to='sponsor_print_logos', validators=[django.core.validators.FileExtensionValidator(['eps', 'epsfepsi', 'svg', 'png'])], verbose_name='Print logo'), + model_name="sponsor", + name="print_logo", + field=models.FileField( + blank=True, + help_text="For printed materials, signage, and projection. SVG or EPS", + null=True, + upload_to="sponsor_print_logos", + validators=[django.core.validators.FileExtensionValidator(["eps", "epsfepsi", "svg", "png"])], + verbose_name="Print logo", + ), ), ] diff --git a/sponsors/migrations/0100_auto_20240107_1054.py b/sponsors/migrations/0100_auto_20240107_1054.py index 8bad2bc92..caa287f23 100644 --- a/sponsors/migrations/0100_auto_20240107_1054.py +++ b/sponsors/migrations/0100_auto_20240107_1054.py @@ -5,25 +5,38 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0099_auto_20231224_1854'), + ("sponsors", "0099_auto_20231224_1854"), ] operations = [ migrations.AddField( - model_name='sponsor', - name='country_of_incorporation', - field=django_countries.fields.CountryField(blank=True, help_text='For contractual purposes', max_length=2, null=True, verbose_name='Country of incorporation (If different)'), + model_name="sponsor", + name="country_of_incorporation", + field=django_countries.fields.CountryField( + blank=True, + help_text="For contractual purposes", + max_length=2, + null=True, + verbose_name="Country of incorporation (If different)", + ), ), migrations.AddField( - model_name='sponsor', - name='state_of_incorporation', - field=models.CharField(blank=True, default='', max_length=64, null=True, verbose_name='US only: State of incorporation (If different)'), + model_name="sponsor", + name="state_of_incorporation", + field=models.CharField( + blank=True, + default="", + max_length=64, + null=True, + verbose_name="US only: State of incorporation (If different)", + ), ), migrations.AlterField( - model_name='sponsor', - name='country', - field=django_countries.fields.CountryField(default='', help_text='For mailing/contact purposes', max_length=2), + model_name="sponsor", + name="country", + field=django_countries.fields.CountryField( + default="", help_text="For mailing/contact purposes", max_length=2 + ), ), ] diff --git a/sponsors/migrations/0101_sponsor_linked_in_page_url.py b/sponsors/migrations/0101_sponsor_linked_in_page_url.py index 61041a08e..870bd07dd 100644 --- a/sponsors/migrations/0101_sponsor_linked_in_page_url.py +++ b/sponsors/migrations/0101_sponsor_linked_in_page_url.py @@ -4,15 +4,16 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0100_auto_20240107_1054'), + ("sponsors", "0100_auto_20240107_1054"), ] operations = [ migrations.AddField( - model_name='sponsor', - name='linked_in_page_url', - field=models.URLField(blank=True, help_text='URL for your LinkedIn page.', null=True, verbose_name='LinkedIn page URL'), + model_name="sponsor", + name="linked_in_page_url", + field=models.URLField( + blank=True, help_text="URL for your LinkedIn page.", null=True, verbose_name="LinkedIn page URL" + ), ), ] diff --git a/sponsors/migrations/0102_auto_20240509_2037.py b/sponsors/migrations/0102_auto_20240509_2037.py index 2c68fa96b..ea5403283 100644 --- a/sponsors/migrations/0102_auto_20240509_2037.py +++ b/sponsors/migrations/0102_auto_20240509_2037.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('sponsors', '0101_sponsor_linked_in_page_url'), + ("sponsors", "0101_sponsor_linked_in_page_url"), ] operations = [ migrations.AlterField( - model_name='textasset', - name='text', - field=models.TextField(blank=True, default=''), + model_name="textasset", + name="text", + field=models.TextField(blank=True, default=""), ), ] diff --git a/sponsors/migrations/0103_alter_benefitfeature_polymorphic_ctype_and_more.py b/sponsors/migrations/0103_alter_benefitfeature_polymorphic_ctype_and_more.py index e9eb9e3a2..2249b6871 100644 --- a/sponsors/migrations/0103_alter_benefitfeature_polymorphic_ctype_and_more.py +++ b/sponsors/migrations/0103_alter_benefitfeature_polymorphic_ctype_and_more.py @@ -6,42 +6,76 @@ class Migration(migrations.Migration): - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), + ("contenttypes", "0002_remove_content_type_name"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('sponsors', '0102_auto_20240509_2037'), + ("sponsors", "0102_auto_20240509_2037"), ] operations = [ migrations.AlterField( - model_name='benefitfeature', - name='polymorphic_ctype', - field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'), + model_name="benefitfeature", + name="polymorphic_ctype", + field=models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="polymorphic_%(app_label)s.%(class)s_set+", + to="contenttypes.contenttype", + ), ), migrations.AlterField( - model_name='benefitfeatureconfiguration', - name='polymorphic_ctype', - field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'), + model_name="benefitfeatureconfiguration", + name="polymorphic_ctype", + field=models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="polymorphic_%(app_label)s.%(class)s_set+", + to="contenttypes.contenttype", + ), ), migrations.AlterField( - model_name='genericasset', - name='polymorphic_ctype', - field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'), + model_name="genericasset", + name="polymorphic_ctype", + field=models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="polymorphic_%(app_label)s.%(class)s_set+", + to="contenttypes.contenttype", + ), ), migrations.AlterField( - model_name='sponsor', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="sponsor", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='sponsor', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="sponsor", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='sponsorshipbenefit', - name='conflicts', - field=models.ManyToManyField(blank=True, help_text='For benefits that conflict with one another,', to='sponsors.sponsorshipbenefit', verbose_name='Conflicts'), + model_name="sponsorshipbenefit", + name="conflicts", + field=models.ManyToManyField( + blank=True, + help_text="For benefits that conflict with one another,", + to="sponsors.sponsorshipbenefit", + verbose_name="Conflicts", + ), ), ] diff --git a/sponsors/models/__init__.py b/sponsors/models/__init__.py index 11f1f1df4..725f3f79d 100644 --- a/sponsors/models/__init__.py +++ b/sponsors/models/__init__.py @@ -7,11 +7,35 @@ from .assets import GenericAsset, ImgAsset, TextAsset, FileAsset, ResponseAsset from .notifications import SponsorEmailNotificationTemplate, SPONSOR_TEMPLATE_HELP_TEXT from .sponsors import Sponsor, SponsorContact, SponsorBenefit -from .benefits import BaseLogoPlacement, BaseTieredBenefit, BaseEmailTargetable, BenefitFeatureConfiguration, \ - LogoPlacementConfiguration, TieredBenefitConfiguration, EmailTargetableConfiguration, BenefitFeature, \ - LogoPlacement, EmailTargetable, TieredBenefit, RequiredImgAsset, RequiredImgAssetConfiguration, \ - RequiredTextAssetConfiguration, RequiredTextAsset, RequiredResponseAssetConfiguration, RequiredResponseAsset, \ - ProvidedTextAssetConfiguration, ProvidedTextAsset, ProvidedFileAssetConfiguration, ProvidedFileAsset -from .sponsorship import Sponsorship, SponsorshipProgram, SponsorshipBenefit, Sponsorship, SponsorshipPackage, \ - SponsorshipCurrentYear +from .benefits import ( + BaseLogoPlacement, + BaseTieredBenefit, + BaseEmailTargetable, + BenefitFeatureConfiguration, + LogoPlacementConfiguration, + TieredBenefitConfiguration, + EmailTargetableConfiguration, + BenefitFeature, + LogoPlacement, + EmailTargetable, + TieredBenefit, + RequiredImgAsset, + RequiredImgAssetConfiguration, + RequiredTextAssetConfiguration, + RequiredTextAsset, + RequiredResponseAssetConfiguration, + RequiredResponseAsset, + ProvidedTextAssetConfiguration, + ProvidedTextAsset, + ProvidedFileAssetConfiguration, + ProvidedFileAsset, +) +from .sponsorship import ( + Sponsorship, + SponsorshipProgram, + SponsorshipBenefit, + Sponsorship, + SponsorshipPackage, + SponsorshipCurrentYear, +) from .contract import LegalClause, Contract, signed_contract_random_path diff --git a/sponsors/models/assets.py b/sponsors/models/assets.py index 9b4899b5a..c5cc2b499 100644 --- a/sponsors/models/assets.py +++ b/sponsors/models/assets.py @@ -2,6 +2,7 @@ This module holds models to store generic assets from Sponsors or Sponsorships """ + import uuid from enum import Enum from pathlib import Path @@ -30,6 +31,7 @@ class GenericAsset(PolymorphicModel): """ Base class used to add required assets to Sponsor or Sponsorship objects """ + objects = GenericAssetQuerySet.as_manager() non_polymorphic = models.Manager() @@ -52,7 +54,7 @@ class Meta: verbose_name = "Asset" verbose_name_plural = "Assets" unique_together = ["content_type", "object_id", "internal_name"] - base_manager_name = 'non_polymorphic' + base_manager_name = "non_polymorphic" @property def value(self): @@ -157,9 +159,7 @@ def choices(cls): class ResponseAsset(GenericAsset): - response = models.CharField( - max_length=32, choices=Response.choices(), blank=False, null=True - ) + response = models.CharField(max_length=32, choices=Response.choices(), blank=False, null=True) def __str__(self): return f"Response Asset: {self.internal_name}" diff --git a/sponsors/models/benefits.py b/sponsors/models/benefits.py index 750f5af6c..7fd38fd74 100644 --- a/sponsors/models/benefits.py +++ b/sponsors/models/benefits.py @@ -1,6 +1,7 @@ """ This module holds models related to benefits features and configurations """ + from django import forms from django.db import models from django.db.models import UniqueConstraint @@ -26,13 +27,13 @@ class BaseLogoPlacement(models.Model): max_length=30, choices=[(c.value, c.name.replace("_", " ").title()) for c in PublisherChoices], verbose_name="Publisher", - help_text="On which site should the logo be displayed?" + help_text="On which site should the logo be displayed?", ) logo_place = models.CharField( max_length=30, choices=[(c.value, c.name.replace("_", " ").title()) for c in LogoPlacementChoices], verbose_name="Logo Placement", - help_text="Where the logo should be placed?" + help_text="Where the logo should be placed?", ) link_to_sponsors_page = models.BooleanField( default=False, @@ -73,7 +74,7 @@ class BaseAsset(models.Model): max_length=30, choices=[(c.value, c.name.replace("_", " ").title()) for c in AssetsRelatedTo], verbose_name="Related To", - help_text="To which instance (Sponsor or Sponsorship) should this asset relate to." + help_text="To which instance (Sponsor or Sponsorship) should this asset relate to.", ) internal_name = models.CharField( max_length=128, @@ -82,15 +83,9 @@ class BaseAsset(models.Model): unique=False, db_index=True, ) - label = models.CharField( - max_length=256, - help_text="What's the title used to display the input to the sponsor?" - ) + label = models.CharField(max_length=256, help_text="What's the title used to display the input to the sponsor?") help_text = models.CharField( - max_length=256, - help_text="Any helper comment on how the input should be populated", - default="", - blank=True + max_length=256, help_text="Any helper comment on how the input should be populated", default="", blank=True ) class Meta: @@ -106,7 +101,7 @@ class Meta: class BaseProvidedAsset(BaseAsset): shared = models.BooleanField( - default = False, + default=False, ) def shared_value(self): @@ -125,8 +120,7 @@ class AssetConfigurationMixin: def create_benefit_feature(self, sponsor_benefit, **kwargs): if not self.ASSET_CLASS: - raise NotImplementedError( - "Subclasses of AssetConfigurationMixin must define an ASSET_CLASS attribute.") + raise NotImplementedError("Subclasses of AssetConfigurationMixin must define an ASSET_CLASS attribute.") # Super: BenefitFeatureConfiguration.create_benefit_feature benefit_feature = super().create_benefit_feature(sponsor_benefit, **kwargs) @@ -138,7 +132,8 @@ def create_benefit_feature(self, sponsor_benefit, **kwargs): asset_qs = content_object.assets.filter(internal_name=self.internal_name) if not asset_qs.exists(): asset = self.ASSET_CLASS( - content_object=content_object, internal_name=self.internal_name, + content_object=content_object, + internal_name=self.internal_name, ) asset.save() @@ -175,14 +170,10 @@ class BaseRequiredTextAsset(BaseRequiredAsset): ASSET_CLASS = TextAsset label = models.CharField( - max_length=256, - help_text="What's the title used to display the text input to the sponsor?" + max_length=256, help_text="What's the title used to display the text input to the sponsor?" ) help_text = models.CharField( - max_length=256, - help_text="Any helper comment on how the input should be populated", - default="", - blank=True + max_length=256, help_text="Any helper comment on how the input should be populated", default="", blank=True ) max_length = models.IntegerField( default=None, @@ -206,14 +197,10 @@ class BaseProvidedTextAsset(BaseProvidedAsset): ASSET_CLASS = TextAsset label = models.CharField( - max_length=256, - help_text="What's the title used to display the text input to the sponsor?" + max_length=256, help_text="What's the title used to display the text input to the sponsor?" ) help_text = models.CharField( - max_length=256, - help_text="Any helper comment on how the input should be populated", - default="", - blank=True + max_length=256, help_text="Any helper comment on how the input should be populated", default="", blank=True ) shared_text = models.TextField(blank=True, null=True) @@ -223,18 +210,13 @@ def shared_value(self): class Meta(BaseProvidedAsset.Meta): abstract = True + class BaseProvidedFileAsset(BaseProvidedAsset): ASSET_CLASS = FileAsset - label = models.CharField( - max_length=256, - help_text="What's the title used to display the file to the sponsor?" - ) + label = models.CharField(max_length=256, help_text="What's the title used to display the file to the sponsor?") help_text = models.CharField( - max_length=256, - help_text="Any helper comment on how the file should be used", - default="", - blank=True + max_length=256, help_text="Any helper comment on how the file should be used", default="", blank=True ) shared_file = models.FileField(blank=True, null=True) @@ -246,7 +228,6 @@ class Meta(BaseProvidedAsset.Meta): class AssetMixin: - def __related_asset(self): """ This method exists to avoid FK relationships between the GenericAsset @@ -276,20 +257,22 @@ def user_edit_url(self): url = reverse("users:update_sponsorship_assets", args=[self.sponsor_benefit.sponsorship.pk]) return url + f"?required_asset={self.pk}" - @property def user_view_url(self): url = reverse("users:view_provided_sponsorship_assets", args=[self.sponsor_benefit.sponsorship.pk]) return url + f"?provided_asset={self.pk}" + class RequiredAssetMixin(AssetMixin): """ This class should be used to implement required assets. It's a mixin to get the information submitted by the user and which is stored in the related asset class. """ + pass + class ProvidedAssetMixin(AssetMixin): """ This class should be used to implement provided assets. @@ -299,10 +282,11 @@ class ProvidedAssetMixin(AssetMixin): @AssetMixin.value.getter def value(self): - if hasattr(self, 'shared') and self.shared: + if hasattr(self, "shared") and self.shared: return self.shared_value() return super().value + ###################################################### # SponsorshipBenefit features configuration models class BenefitFeatureConfiguration(PolymorphicModel): @@ -317,7 +301,7 @@ class BenefitFeatureConfiguration(PolymorphicModel): class Meta: verbose_name = "Benefit Feature Configuration" verbose_name_plural = "Benefit Feature Configurations" - base_manager_name = 'non_polymorphic' + base_manager_name = "non_polymorphic" @property def benefit_feature_class(self): @@ -339,7 +323,7 @@ def get_cfg_kwargs(self, **kwargs): for field in benefit_fields: # Skip the OneToOne rel from the base class to BenefitFeatureConfiguration base class # since this field only exists in child models - if BenefitFeatureConfiguration is getattr(field, 'related_model', None): + if BenefitFeatureConfiguration is getattr(field, "related_model", None): continue # Skip if field config is being externally overwritten elif field.name in kwargs: @@ -469,8 +453,7 @@ def benefit_feature_class(self): return RequiredImgAsset -class RequiredTextAssetConfiguration(AssetConfigurationMixin, BaseRequiredTextAsset, - BenefitFeatureConfiguration): +class RequiredTextAssetConfiguration(AssetConfigurationMixin, BaseRequiredTextAsset, BenefitFeatureConfiguration): class Meta(BaseRequiredTextAsset.Meta, BenefitFeatureConfiguration.Meta): verbose_name = "Require Text Configuration" verbose_name_plural = "Require Text Configurations" @@ -490,9 +473,7 @@ class RequiredResponseAssetConfiguration( class Meta(BaseRequiredResponseAsset.Meta, BenefitFeatureConfiguration.Meta): verbose_name = "Require Response Configuration" verbose_name_plural = "Require Response Configurations" - constraints = [ - UniqueConstraint(fields=["internal_name"], name="uniq_response_asset_cfg") - ] + constraints = [UniqueConstraint(fields=["internal_name"], name="uniq_response_asset_cfg")] def __str__(self): return f"Require response configuration" @@ -502,9 +483,7 @@ def benefit_feature_class(self): return RequiredResponseAsset -class ProvidedTextAssetConfiguration( - AssetConfigurationMixin, BaseProvidedTextAsset, BenefitFeatureConfiguration -): +class ProvidedTextAssetConfiguration(AssetConfigurationMixin, BaseProvidedTextAsset, BenefitFeatureConfiguration): class Meta(BaseProvidedTextAsset.Meta, BenefitFeatureConfiguration.Meta): verbose_name = "Provided Text Configuration" verbose_name_plural = "Provided Text Configurations" @@ -518,8 +497,7 @@ def benefit_feature_class(self): return ProvidedTextAsset -class ProvidedFileAssetConfiguration(AssetConfigurationMixin, BaseProvidedFileAsset, - BenefitFeatureConfiguration): +class ProvidedFileAssetConfiguration(AssetConfigurationMixin, BaseProvidedFileAsset, BenefitFeatureConfiguration): class Meta(BaseProvidedFileAsset.Meta, BenefitFeatureConfiguration.Meta): verbose_name = "Provided File Configuration" verbose_name_plural = "Provided File Configurations" @@ -539,6 +517,7 @@ class BenefitFeature(PolymorphicModel): """ Base class for sponsor benefits features. """ + objects = BenefitFeatureQuerySet.as_manager() non_polymorphic = models.Manager() @@ -547,7 +526,7 @@ class BenefitFeature(PolymorphicModel): class Meta: verbose_name = "Benefit Feature" verbose_name_plural = "Benefit Features" - base_manager_name = 'non_polymorphic' + base_manager_name = "non_polymorphic" def display_modifier(self, name, **kwargs): return name @@ -607,7 +586,9 @@ def as_form_field(self, **kwargs): help_text = kwargs.pop("help_text", self.help_text) label = kwargs.pop("label", self.label) required = kwargs.pop("required", False) - return forms.ImageField(required=required, help_text=help_text, label=label, widget=forms.ClearableFileInput, **kwargs) + return forms.ImageField( + required=required, help_text=help_text, label=label, widget=forms.ClearableFileInput, **kwargs + ) class RequiredTextAsset(RequiredAssetMixin, BaseRequiredTextAsset, BenefitFeature): @@ -641,7 +622,14 @@ def as_form_field(self, **kwargs): help_text = kwargs.pop("help_text", self.help_text) label = kwargs.pop("label", self.label) required = kwargs.pop("required", False) - return forms.ChoiceField(required=required, choices=Response.choices(), widget=forms.RadioSelect, help_text=help_text, label=label, **kwargs) + return forms.ChoiceField( + required=required, + choices=Response.choices(), + widget=forms.RadioSelect, + help_text=help_text, + label=label, + **kwargs, + ) class ProvidedTextAsset(ProvidedAssetMixin, BaseProvidedTextAsset, BenefitFeature): diff --git a/sponsors/models/contract.py b/sponsors/models/contract.py index 3cbf389e2..32c77959a 100644 --- a/sponsors/models/contract.py +++ b/sponsors/models/contract.py @@ -1,6 +1,7 @@ """ This module holds models related to the process to generate contracts """ + import uuid from itertools import chain from pathlib import Path @@ -32,9 +33,7 @@ class LegalClause(OrderedModel): help_text="Legal clause text to be added to contract", blank=False, ) - notes = models.TextField( - verbose_name="Notes", help_text="PSF staff notes", blank=True, default="" - ) + notes = models.TextField(verbose_name="Notes", help_text="PSF staff notes", blank=True, default="") def __str__(self): return f"Clause: {self.internal_name}" @@ -84,9 +83,7 @@ class Contract(models.Model): FINAL_VERSION_DOCX_DIR = FINAL_VERSION_PDF_DIR + "docx/" SIGNED_PDF_DIR = FINAL_VERSION_PDF_DIR + "signed/" - status = models.CharField( - max_length=20, choices=STATUS_CHOICES, default=DRAFT, db_index=True - ) + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=DRAFT, db_index=True) revision = models.PositiveIntegerField(default=0, verbose_name="Revision nº") document = models.FileField( upload_to=FINAL_VERSION_PDF_DIR, @@ -175,9 +172,7 @@ def new(cls, sponsorship): item += f" {index_str}" benefits_list.append(item) - legal_clauses_text = "\n".join( - [f"[^{i}]: {c.clause}" for i, c in enumerate(legal_clauses, start=1)] - ) + legal_clauses_text = "\n".join([f"[^{i}]: {c.clause}" for i, c in enumerate(legal_clauses, start=1)]) return cls.objects.create( sponsorship=sponsorship, sponsor_info=sponsor_info, diff --git a/sponsors/models/managers.py b/sponsors/models/managers.py index 5cb241fc9..5e15aa30f 100644 --- a/sponsors/models/managers.py +++ b/sponsors/models/managers.py @@ -16,12 +16,12 @@ def approved(self): return self.filter(status=self.model.APPROVED) def visible_to(self, user): - contacts = user.sponsorcontact_set.values_list('sponsor_id', flat=True) + contacts = user.sponsorcontact_set.values_list("sponsor_id", flat=True) status = [self.model.APPLIED, self.model.APPROVED, self.model.FINALIZED] return self.filter( Q(submited_by=user) | Q(sponsor_id__in=Subquery(contacts)), status__in=status, - ).select_related('sponsor') + ).select_related("sponsor") def finalized(self): return self.filter(status=self.model.FINALIZED) @@ -36,23 +36,28 @@ def enabled(self): def with_logo_placement(self, logo_place=None, publisher=None): from sponsors.models import LogoPlacement, SponsorBenefit + feature_qs = LogoPlacement.objects.all() if logo_place: feature_qs = feature_qs.filter(logo_place=logo_place) if publisher: feature_qs = feature_qs.filter(publisher=publisher) - benefit_qs = SponsorBenefit.objects.filter(id__in=Subquery(feature_qs.values_list('sponsor_benefit_id', flat=True))) - return self.filter(id__in=Subquery(benefit_qs.values_list('sponsorship_id', flat=True))) + benefit_qs = SponsorBenefit.objects.filter( + id__in=Subquery(feature_qs.values_list("sponsor_benefit_id", flat=True)) + ) + return self.filter(id__in=Subquery(benefit_qs.values_list("sponsorship_id", flat=True))) def includes_benefit_feature(self, feature_model): from sponsors.models import SponsorBenefit + feature_qs = feature_model.objects.all() - benefit_qs = SponsorBenefit.objects.filter(id__in=Subquery(feature_qs.values_list('sponsor_benefit_id', flat=True))) - return self.filter(id__in=Subquery(benefit_qs.values_list('sponsorship_id', flat=True))) + benefit_qs = SponsorBenefit.objects.filter( + id__in=Subquery(feature_qs.values_list("sponsor_benefit_id", flat=True)) + ) + return self.filter(id__in=Subquery(benefit_qs.values_list("sponsorship_id", flat=True))) class SponsorshipCurrentYearQuerySet(QuerySet): - def delete(self): raise IntegrityError("Singleton object cannot be delete. Try updating it instead.") @@ -89,7 +94,11 @@ def without_conflicts(self): return self.filter(conflicts__isnull=True) def a_la_carte(self): - return self.annotate(num_packages=Count("packages")).filter(num_packages=0, standalone=False).exclude(unavailable=True) + return ( + self.annotate(num_packages=Count("packages")) + .filter(num_packages=0, standalone=False) + .exclude(unavailable=True) + ) def standalone(self): return self.filter(standalone=True).exclude(unavailable=True) @@ -107,6 +116,7 @@ def from_year(self, year): def from_current_year(self): from sponsors.models import SponsorshipCurrentYear + current_year = SponsorshipCurrentYear.get_year() return self.from_year(current_year) @@ -120,12 +130,12 @@ def from_year(self, year): def from_current_year(self): from sponsors.models import SponsorshipCurrentYear + current_year = SponsorshipCurrentYear.get_year() return self.from_year(current_year) class BenefitFeatureQuerySet(PolymorphicQuerySet): - def delete(self): if not self.polymorphic_disabled: return self.non_polymorphic().delete() @@ -137,17 +147,18 @@ def from_sponsorship(self, sponsorship): def required_assets(self): from sponsors.models.benefits import RequiredAssetMixin + required_assets_classes = RequiredAssetMixin.__subclasses__() return self.instance_of(*required_assets_classes).select_related("sponsor_benefit__sponsorship") def provided_assets(self): from sponsors.models.benefits import ProvidedAssetMixin + provided_assets_classes = ProvidedAssetMixin.__subclasses__() return self.instance_of(*provided_assets_classes).select_related("sponsor_benefit__sponsorship") class BenefitFeatureConfigurationQuerySet(PolymorphicQuerySet): - def delete(self): if not self.polymorphic_disabled: return self.non_polymorphic().delete() @@ -156,8 +167,8 @@ def delete(self): class GenericAssetQuerySet(PolymorphicQuerySet): - def all_assets(self): from sponsors.models import GenericAsset + classes = GenericAsset.all_asset_types() return self.select_related("content_type").instance_of(*classes) diff --git a/sponsors/models/sponsors.py b/sponsors/models/sponsors.py index 78d5d6e32..a656718d0 100644 --- a/sponsors/models/sponsors.py +++ b/sponsors/models/sponsors.py @@ -1,6 +1,7 @@ """ This module holds models related to the Sponsor entity. """ + from allauth.account.models import EmailAddress from django.conf import settings from django.core.validators import FileExtensionValidator @@ -36,7 +37,7 @@ class Sponsor(ContentManageable): null=True, verbose_name="Landing page URL", help_text="Landing page URL. This may be provided by the sponsor, however the linked page may not contain any " - "sales or marketing information.", + "sales or marketing information.", ) twitter_handle = models.CharField( max_length=32, # Actual limit set by twitter is 15 characters, but that may change? @@ -45,20 +46,17 @@ class Sponsor(ContentManageable): verbose_name="Twitter handle", ) linked_in_page_url = models.URLField( - blank=True, - null=True, - verbose_name="LinkedIn page URL", - help_text="URL for your LinkedIn page." + blank=True, null=True, verbose_name="LinkedIn page URL", help_text="URL for your LinkedIn page." ) web_logo = models.ImageField( upload_to="sponsor_web_logos", verbose_name="Web logo", help_text="For display on our sponsor webpage. High resolution PNG or JPG, smallest dimension no less than " - "256px", + "256px", ) print_logo = models.FileField( upload_to="sponsor_print_logos", - validators=[FileExtensionValidator(['eps', 'epsf' 'epsi', 'svg', 'png'])], + validators=[FileExtensionValidator(["eps", "epsf" "epsi", "svg", "png"])], blank=True, null=True, verbose_name="Print logo", @@ -66,27 +64,23 @@ class Sponsor(ContentManageable): ) primary_phone = models.CharField("Primary Phone", max_length=32) - mailing_address_line_1 = models.CharField( - verbose_name="Mailing Address line 1", max_length=128, default="" - ) + mailing_address_line_1 = models.CharField(verbose_name="Mailing Address line 1", max_length=128, default="") mailing_address_line_2 = models.CharField( verbose_name="Mailing Address line 2", max_length=128, blank=True, default="" ) city = models.CharField(verbose_name="City", max_length=64, default="") - state = models.CharField( - verbose_name="State/Province/Region", max_length=64, blank=True, default="" - ) - postal_code = models.CharField( - verbose_name="Zip/Postal Code", max_length=64, default="" - ) + state = models.CharField(verbose_name="State/Province/Region", max_length=64, blank=True, default="") + postal_code = models.CharField(verbose_name="Zip/Postal Code", max_length=64, default="") country = CountryField(default="", help_text="For mailing/contact purposes") assets = GenericRelation(GenericAsset) country_of_incorporation = CountryField( - verbose_name="Country of incorporation (If different)", help_text="For contractual purposes", blank=True, null=True + verbose_name="Country of incorporation (If different)", + help_text="For contractual purposes", + blank=True, + null=True, ) state_of_incorporation = models.CharField( - verbose_name="US only: State of incorporation (If different)", - max_length=64, blank=True, null=True, default="" + verbose_name="US only: State of incorporation (If different)", max_length=64, blank=True, null=True, default="" ) class Meta: @@ -96,9 +90,7 @@ class Meta: def verified_emails(self, initial_emails=None): emails = initial_emails if initial_emails is not None else [] for contact in self.contacts.all(): - if EmailAddress.objects.filter( - email__iexact=contact.email, verified=True - ).exists(): + if EmailAddress.objects.filter(email__iexact=contact.email, verified=True).exists(): emails.append(contact.email) return list(set({e.casefold(): e for e in emails}.values())) @@ -132,6 +124,7 @@ class SponsorContact(models.Model): """ Sponsor contact information """ + PRIMARY_CONTACT = "primary" ADMINISTRATIVE_CONTACT = "administrative" ACCOUTING_CONTACT = "accounting" @@ -145,24 +138,20 @@ class SponsorContact(models.Model): objects = SponsorContactQuerySet.as_manager() - sponsor = models.ForeignKey( - "Sponsor", on_delete=models.CASCADE, related_name="contacts" - ) + sponsor = models.ForeignKey("Sponsor", on_delete=models.CASCADE, related_name="contacts") user = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.CASCADE ) # Optionally related to a User! (This needs discussion) primary = models.BooleanField( default=False, help_text="The primary contact for a sponsorship will be responsible for managing deliverables we need to " - "fulfill benefits. Primary contacts will receive all email notifications regarding sponsorship. " + "fulfill benefits. Primary contacts will receive all email notifications regarding sponsorship. ", ) administrative = models.BooleanField( - default=False, - help_text="Administrative contacts will only be notified regarding contracts." + default=False, help_text="Administrative contacts will only be notified regarding contracts." ) accounting = models.BooleanField( - default=False, - help_text="Accounting contacts will only be notified regarding invoices and payments." + default=False, help_text="Accounting contacts will only be notified regarding invoices and payments." ) manager = models.BooleanField( default=False, @@ -181,15 +170,15 @@ def can_manage(self): @property def type(self): - types=[] + types = [] if self.primary: - types.append('Primary') + types.append("Primary") if self.administrative: - types.append('Administrative') + types.append("Administrative") if self.manager: - types.append('Manager') + types.append("Manager") if self.accounting: - types.append('Accounting') + types.append("Accounting") return ", ".join(types) def __str__(self): @@ -202,20 +191,16 @@ class SponsorBenefit(OrderedModel): Created after a new sponsorship """ - sponsorship = models.ForeignKey( - 'sponsors.Sponsorship', on_delete=models.CASCADE, related_name="benefits" - ) + sponsorship = models.ForeignKey("sponsors.Sponsorship", on_delete=models.CASCADE, related_name="benefits") sponsorship_benefit = models.ForeignKey( - 'sponsors.SponsorshipBenefit', + "sponsors.SponsorshipBenefit", null=True, blank=False, on_delete=models.SET_NULL, help_text="Sponsorship Benefit this Sponsor Benefit came from", ) program_name = models.CharField( - max_length=1024, - verbose_name="Program Name", - help_text="For display in the contract and sponsor dashboard." + max_length=1024, verbose_name="Program Name", help_text="For display in the contract and sponsor dashboard." ) name = models.CharField( max_length=1024, @@ -229,7 +214,7 @@ class SponsorBenefit(OrderedModel): help_text="For display in the contract and sponsor dashboard.", ) program = models.ForeignKey( - 'sponsors.SponsorshipProgram', + "sponsors.SponsorshipProgram", null=True, blank=False, on_delete=models.SET_NULL, @@ -242,12 +227,8 @@ class SponsorBenefit(OrderedModel): verbose_name="Benefit Internal Value", help_text="Benefit's internal value from when the Sponsorship gets created", ) - added_by_user = models.BooleanField( - blank=True, default=False, verbose_name="Added by user?" - ) - standalone = models.BooleanField( - blank=True, default=False, verbose_name="Added as standalone benefit?" - ) + added_by_user = models.BooleanField(blank=True, default=False, verbose_name="Added by user?") + standalone = models.BooleanField(blank=True, default=False, verbose_name="Added as standalone benefit?") def __str__(self): if self.program is not None: diff --git a/sponsors/models/sponsorship.py b/sponsors/models/sponsorship.py index d230e91c3..b3de037d4 100644 --- a/sponsors/models/sponsorship.py +++ b/sponsors/models/sponsorship.py @@ -1,6 +1,7 @@ """ This module holds models related to the Sponsorship entity. """ + from datetime import date from itertools import chain @@ -19,17 +20,24 @@ from ordered_model.models import OrderedModel -from sponsors.exceptions import SponsorWithExistingApplicationException, InvalidStatusException, \ - SponsorshipInvalidDateRangeException +from sponsors.exceptions import ( + SponsorWithExistingApplicationException, + InvalidStatusException, + SponsorshipInvalidDateRangeException, +) from sponsors.models.assets import GenericAsset -from sponsors.models.managers import SponsorshipPackageQuerySet, SponsorshipBenefitQuerySet, \ - SponsorshipQuerySet, SponsorshipCurrentYearQuerySet +from sponsors.models.managers import ( + SponsorshipPackageQuerySet, + SponsorshipBenefitQuerySet, + SponsorshipQuerySet, + SponsorshipCurrentYearQuerySet, +) from sponsors.models.benefits import TieredBenefitConfiguration from sponsors.models.sponsors import SponsorBenefit YEAR_VALIDATORS = [ - MinValueValidator(limit_value=2022, message="The min year value is 2022."), - MaxValueValidator(limit_value=2050, message="The max year value is 2050."), + MinValueValidator(limit_value=2022, message="The min year value is 2022."), + MaxValueValidator(limit_value=2050, message="The max year value is 2050."), ] @@ -37,17 +45,22 @@ class SponsorshipPackage(OrderedModel): """ Represent default packages of benefits (visionary, sustainability etc) """ + objects = SponsorshipPackageQuerySet.as_manager() name = models.CharField(max_length=64) sponsorship_amount = models.PositiveIntegerField() - advertise = models.BooleanField(default=False, blank=True, help_text="If checked, this package will be advertised " - "in the sponsosrhip application") - logo_dimension = models.PositiveIntegerField(default=175, blank=True, help_text="Internal value used to control " - "logos dimensions at sponsors " - "page") - slug = models.SlugField(db_index=True, blank=False, null=False, help_text="Internal identifier used " - "to reference this package.") + advertise = models.BooleanField( + default=False, + blank=True, + help_text="If checked, this package will be advertised " "in the sponsosrhip application", + ) + logo_dimension = models.PositiveIntegerField( + default=175, blank=True, help_text="Internal value used to control " "logos dimensions at sponsors " "page" + ) + slug = models.SlugField( + db_index=True, blank=False, null=False, help_text="Internal identifier used " "to reference this package." + ) year = models.PositiveIntegerField(null=True, validators=YEAR_VALIDATORS, db_index=True) allow_a_la_carte = models.BooleanField( @@ -55,10 +68,13 @@ class SponsorshipPackage(OrderedModel): ) def __str__(self): - return f'{self.name} ({self.year})' + return f"{self.name} ({self.year})" class Meta: - ordering = ('-year', 'order',) + ordering = ( + "-year", + "order", + ) def has_user_customization(self, benefits): """ @@ -67,9 +83,7 @@ def has_user_customization(self, benefits): pkg_benefits_with_conflicts = set(self.benefits.with_conflicts()) # check if all packages' benefits without conflict are present in benefits list - from_pkg_benefits = { - b for b in benefits if b not in pkg_benefits_with_conflicts - } + from_pkg_benefits = {b for b in benefits if b not in pkg_benefits_with_conflicts} if from_pkg_benefits != set(self.benefits.without_conflicts()): return True @@ -86,9 +100,7 @@ def has_user_customization(self, benefits): grp = set([pkg_benefit] + list(pkg_benefit.conflicts.all())) conflicts_groups.append(grp) - has_all_conflicts = all( - g.intersection(remaining_benefits) for g in conflicts_groups - ) + has_all_conflicts = all(g.intersection(remaining_benefits) for g in conflicts_groups) return not has_all_conflicts def get_user_customization(self, benefits): @@ -98,8 +110,8 @@ def get_user_customization(self, benefits): benefits = set(tuple(benefits)) pkg_benefits = set(tuple(self.benefits.all())) return { - "added_by_user": benefits - pkg_benefits, - "removed_by_user": pkg_benefits - benefits, + "added_by_user": benefits - pkg_benefits, + "removed_by_user": pkg_benefits - benefits, } def clone(self, year: int): @@ -113,9 +125,7 @@ def clone(self, year: int): "logo_dimension": self.logo_dimension, "order": self.order, } - return SponsorshipPackage.objects.get_or_create( - slug=self.slug, year=year, defaults=defaults - ) + return SponsorshipPackage.objects.get_or_create(slug=self.slug, year=year, defaults=defaults) def get_default_revenue_split(self) -> list[tuple[str, float]]: """ @@ -166,13 +176,9 @@ class Sponsorship(models.Model): objects = SponsorshipQuerySet.as_manager() - submited_by = models.ForeignKey( - settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL - ) + submited_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL) sponsor = models.ForeignKey("Sponsor", null=True, on_delete=models.SET_NULL) - status = models.CharField( - max_length=20, choices=STATUS_CHOICES, default=APPLIED, db_index=True - ) + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=APPLIED, db_index=True) locked = models.BooleanField(default=False) start_date = models.DateField(null=True, blank=True) @@ -187,17 +193,20 @@ class Sponsorship(models.Model): default=False, help_text="If true, it means the user customized the package's benefits. Changes are listed under section 'User Customizations'.", ) - level_name_old = models.CharField(max_length=64, default="", blank=True, help_text="DEPRECATED: shall be removed " - "after manual data sanity " - "check.", verbose_name="Level " - "name") + level_name_old = models.CharField( + max_length=64, + default="", + blank=True, + help_text="DEPRECATED: shall be removed " "after manual data sanity " "check.", + verbose_name="Level " "name", + ) package = models.ForeignKey(SponsorshipPackage, null=True, on_delete=models.SET_NULL) sponsorship_fee = models.PositiveIntegerField(null=True, blank=True) overlapped_by = models.ForeignKey("self", null=True, on_delete=models.SET_NULL) renewal = models.BooleanField( null=True, blank=True, - help_text="If true, it means the sponsorship is a renewal of a previous sponsorship and will use the renewal template for contracting." + help_text="If true, it means the sponsorship is a renewal of a previous sponsorship and will use the renewal template for contracting.", ) assets = GenericRelation(GenericAsset) @@ -266,20 +275,13 @@ def new(cls, sponsor, benefits, package=None, submited_by=None): for benefit in benefits: added_by_user = for_modified_package and benefit not in package_benefits - SponsorBenefit.new_copy( - benefit, sponsorship=sponsorship, added_by_user=added_by_user - ) + SponsorBenefit.new_copy(benefit, sponsorship=sponsorship, added_by_user=added_by_user) return sponsorship @property def estimated_cost(self): - return ( - self.benefits.aggregate(Sum("benefit_internal_value"))[ - "benefit_internal_value__sum" - ] - or 0 - ) + return self.benefits.aggregate(Sum("benefit_internal_value"))["benefit_internal_value__sum"] or 0 @property def verbose_sponsorship_fee(self): @@ -293,7 +295,9 @@ def agreed_fee(self): if self.status in valid_status: return self.sponsorship_fee try: - benefits = [sb.sponsorship_benefit for sb in self.package_benefits.all().select_related('sponsorship_benefit')] + benefits = [ + sb.sponsorship_benefit for sb in self.package_benefits.all().select_related("sponsorship_benefit") + ] if self.package and not self.package.has_user_customization(benefits): return self.sponsorship_fee except SponsorshipPackage.DoesNotExist: # sponsorship level names can change over time @@ -301,10 +305,7 @@ def agreed_fee(self): @property def is_active(self): - conditions = [ - self.status == self.FINALIZED, - self.end_date and self.end_date > date.today() - ] + conditions = [self.status == self.FINALIZED, self.end_date and self.end_date > date.today()] def reject(self): if self.REJECTED not in self.next_status: @@ -365,9 +366,7 @@ def admin_url(self): def contract_admin_url(self): if not self.contract: return "" - return reverse( - "admin:sponsors_contract_change", args=[self.contract.pk] - ) + return reverse("admin:sponsors_contract_change", args=[self.contract.pk]) @property def detail_url(self): @@ -397,8 +396,8 @@ def next_status(self): @property def previous_effective_date(self): - if len(self.sponsor.sponsorship_set.all().order_by('-year')) > 1: - return self.sponsor.sponsorship_set.all().order_by('-year')[1].start_date + if len(self.sponsor.sponsorship_set.all().order_by("-year")) > 1: + return self.sponsor.sponsorship_set.all().order_by("-year")[1].start_date return None @@ -517,11 +516,7 @@ def unavailability_message(self): def has_capacity(self): if self.unavailable: return False - return not ( - self.remaining_capacity is not None - and self.remaining_capacity <= 0 - and not self.soft_capacity - ) + return not (self.remaining_capacity is not None and self.remaining_capacity <= 0 and not self.soft_capacity) @property def remaining_capacity(self): @@ -575,9 +570,7 @@ def clone(self, year: int): "soft_capacity": self.soft_capacity, "order": self.order, } - new_benefit, created = SponsorshipBenefit.objects.get_or_create( - name=self.name, year=year, defaults=defaults - ) + new_benefit, created = SponsorshipBenefit.objects.get_or_create(name=self.name, year=year, defaults=defaults) # if new, all related objects should be cloned too if created: @@ -600,12 +593,13 @@ class SponsorshipCurrentYear(models.Model): The sponsorship_current_year_singleton_idx introduced by migration 0079 in sponsors app enforces the singleton at DB level. """ + CACHE_KEY = "current_year" objects = SponsorshipCurrentYearQuerySet.as_manager() year = models.PositiveIntegerField( validators=YEAR_VALIDATORS, - help_text="Every new sponsorship application will be considered as an application from to the active year." + help_text="Every new sponsorship application will be considered as an application from to the active year.", ) def __str__(self): diff --git a/sponsors/notifications.py b/sponsors/notifications.py index 196cc94b6..d7c417cf7 100644 --- a/sponsors/notifications.py +++ b/sponsors/notifications.py @@ -132,37 +132,32 @@ def add_log_entry(request, object, acton_flag, message): object_id=object.pk, object_repr=str(object), action_flag=acton_flag, - change_message=message + change_message=message, ) class SponsorshipApprovalLogger: - def notify(self, request, sponsorship, contract, **kwargs): add_log_entry(request, sponsorship, CHANGE, "Sponsorship Approval") add_log_entry(request, contract, ADDITION, "Created After Sponsorship Approval") class SentContractLogger: - def notify(self, request, contract, **kwargs): add_log_entry(request, contract, CHANGE, "Contract Sent") class ExecutedContractLogger: - def notify(self, request, contract, **kwargs): add_log_entry(request, contract, CHANGE, "Contract Executed") class ExecutedExistingContractLogger: - def notify(self, request, contract, **kwargs): add_log_entry(request, contract, CHANGE, "Existing Contract Uploaded and Executed") class NullifiedContractLogger: - def notify(self, request, contract, **kwargs): add_log_entry(request, contract, CHANGE, "Contract Nullified") @@ -195,7 +190,6 @@ def get_email_context(self, **kwargs): class ClonedResourcesLogger: - def notify(self, request, resource, from_year, **kwargs): msg = f"Cloned from {from_year} sponsorship application config" add_log_entry(request, resource, ADDITION, msg) diff --git a/sponsors/pandoc_filters/pagebreak.py b/sponsors/pandoc_filters/pagebreak.py index 22a786a2b..c4aea60df 100644 --- a/sponsors/pandoc_filters/pagebreak.py +++ b/sponsors/pandoc_filters/pagebreak.py @@ -6,19 +6,19 @@ # Revision: c8cddccebb78af75168da000a3d6ac09349bef73 # ------------------------------------------------------------------------------ # MIT License -# +# # Copyright (c) 2018 pandocker -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -28,7 +28,7 @@ # SOFTWARE. # ------------------------------------------------------------------------------ -""" pandoc-docx-pagebreakpy +"""pandoc-docx-pagebreakpy Pandoc filter to insert pagebreak as openxml RawBlock Only for docx output @@ -40,10 +40,12 @@ class DocxPagebreak(object): - pagebreak = pf.RawBlock("<w:p><w:r><w:br w:type=\"page\" /></w:r></w:p>", format="openxml") - sectionbreak = pf.RawBlock("<w:p><w:pPr><w:sectPr><w:type w:val=\"nextPage\" /></w:sectPr></w:pPr></w:p>", - format="openxml") - toc = pf.RawBlock(r""" + pagebreak = pf.RawBlock('<w:p><w:r><w:br w:type="page" /></w:r></w:p>', format="openxml") + sectionbreak = pf.RawBlock( + '<w:p><w:pPr><w:sectPr><w:type w:val="nextPage" /></w:sectPr></w:pPr></w:p>', format="openxml" + ) + toc = pf.RawBlock( + r""" <w:sdt> <w:sdtContent xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"> <w:p> @@ -56,12 +58,14 @@ class DocxPagebreak(object): </w:p> </w:sdtContent> </w:sdt> -""", format="openxml") +""", + format="openxml", + ) def action(self, elem, doc): if isinstance(elem, pf.RawBlock): if elem.text == r"\newpage": - if (doc.format == "docx"): + if doc.format == "docx": elem = self.pagebreak # elif elem.text == r"\newsection": # if (doc.format == "docx"): @@ -70,7 +74,7 @@ def action(self, elem, doc): # else: # elem = [] elif elem.text == r"\toc": - if (doc.format == "docx"): + if doc.format == "docx": pf.debug("Table of Contents") para = [pf.Para(pf.Str("Table"), pf.Space(), pf.Str("of"), pf.Space(), pf.Str("Contents"))] div = pf.Div(*para, attributes={"custom-style": "TOC Heading"}) diff --git a/sponsors/serializers.py b/sponsors/serializers.py index c0782c12a..a41dc6859 100644 --- a/sponsors/serializers.py +++ b/sponsors/serializers.py @@ -1,9 +1,9 @@ - from rest_framework import serializers from sponsors.models import GenericAsset from sponsors.models.enums import PublisherChoices, LogoPlacementChoices + class LogoPlacementSerializer(serializers.Serializer): publisher = serializers.CharField() flight = serializers.CharField() diff --git a/sponsors/templatetags/sponsors.py b/sponsors/templatetags/sponsors.py index 7e2f1f462..0d13e691a 100644 --- a/sponsors/templatetags/sponsors.py +++ b/sponsors/templatetags/sponsors.py @@ -11,6 +11,7 @@ register = template.Library() + @register.inclusion_tag("sponsors/partials/full_sponsorship.txt") def full_sponsorship(sponsorship, display_fee=False): if not display_fee: @@ -25,14 +26,17 @@ def full_sponsorship(sponsorship, display_fee=False): @register.inclusion_tag("sponsors/partials/sponsors-list.html") def list_sponsors(logo_place, publisher=PublisherChoices.FOUNDATION.value): - sponsorships = Sponsorship.objects.enabled().with_logo_placement( - logo_place=logo_place, publisher=publisher - ).order_by('package').select_related('sponsor', 'package') + sponsorships = ( + Sponsorship.objects.enabled() + .with_logo_placement(logo_place=logo_place, publisher=publisher) + .order_by("package") + .select_related("sponsor", "package") + ) packages = SponsorshipPackage.objects.all() context = { - 'logo_place': logo_place, - 'sponsorships': sponsorships, + "logo_place": logo_place, + "sponsorships": sponsorships, } # organizes logo placement for sponsors page @@ -44,26 +48,22 @@ def list_sponsors(logo_place, publisher=PublisherChoices.FOUNDATION.value): sponsorships_by_package[pkg.slug] = { "label": pkg.name, "logo_dimension": str(pkg.logo_dimension), - "sponsorships": [ - sp - for sp in sponsorships - if sp.package.slug == pkg.slug - ] + "sponsorships": [sp for sp in sponsorships if sp.package.slug == pkg.slug], } - context.update({ - 'packages': SponsorshipPackage.objects.all(), - 'sponsorships_by_package': sponsorships_by_package, - }) + context.update( + { + "packages": SponsorshipPackage.objects.all(), + "sponsorships_by_package": sponsorships_by_package, + } + ) return context @register.simple_tag def benefit_quantity_for_package(benefit, package): - quantity_configuration = TieredBenefitConfiguration.objects.filter( - benefit=benefit, package=package - ).first() + quantity_configuration = TieredBenefitConfiguration.objects.filter(benefit=benefit, package=package).first() if quantity_configuration is None: return "" return quantity_configuration.display_label or quantity_configuration.quantity @@ -84,6 +84,4 @@ def ideal_size(image, ideal_dimension): # this is just a fallback to return ideal_dimension instead w, h = ideal_dimension, ideal_dimension - return int( - w * math.sqrt((100 * ideal_dimension) / (w * h)) - ) + return int(w * math.sqrt((100 * ideal_dimension) / (w * h))) diff --git a/sponsors/tests/baker_recipes.py b/sponsors/tests/baker_recipes.py index 8d24820d6..9e6fd7367 100644 --- a/sponsors/tests/baker_recipes.py +++ b/sponsors/tests/baker_recipes.py @@ -28,9 +28,7 @@ status=Contract.AWAITING_SIGNATURE, ) -package = Recipe( - SponsorshipPackage -) +package = Recipe(SponsorshipPackage) finalized_sponsorship = Recipe( Sponsorship, diff --git a/sponsors/tests/test_admin.py b/sponsors/tests/test_admin.py index 1e94fa6df..a6425e90a 100644 --- a/sponsors/tests/test_admin.py +++ b/sponsors/tests/test_admin.py @@ -8,16 +8,13 @@ from sponsors.admin import SponsorshipStatusListFilter, SponsorshipAdmin from sponsors.models import Sponsorship -class TestCustomSponsorshipStatusListFilter(TestCase): +class TestCustomSponsorshipStatusListFilter(TestCase): def setUp(self): self.request = RequestFactory().get("/") self.model_admin = SponsorshipAdmin self.filter = SponsorshipStatusListFilter( - request=self.request, - params={}, - model=Sponsorship, - model_admin=self.model_admin + request=self.request, params={}, model=Sponsorship, model_admin=self.model_admin ) def test_basic_configuration(self): diff --git a/sponsors/tests/test_api.py b/sponsors/tests/test_api.py index 3575e59e6..76e9adc82 100644 --- a/sponsors/tests/test_api.py +++ b/sponsors/tests/test_api.py @@ -17,21 +17,26 @@ class LogoPlacementeAPIListTests(APITestCase): url = reverse_lazy("logo_placement_list") def setUp(self): - self.user = baker.make('users.User') + self.user = baker.make("users.User") token = Token.objects.get(user=self.user) - self.permission = Permission.objects.get(name='Can access sponsor placement API') + self.permission = Permission.objects.get(name="Can access sponsor placement API") self.user.user_permissions.add(self.permission) - self.authorization = f'Token {token.key}' + self.authorization = f"Token {token.key}" self.sponsors = baker.make(Sponsor, _create_files=True, _quantity=3) - sponsorships = baker.make_recipe("sponsors.tests.finalized_sponsorship", sponsor=iter(self.sponsors), - _quantity=3) + sponsorships = baker.make_recipe( + "sponsors.tests.finalized_sponsorship", sponsor=iter(self.sponsors), _quantity=3 + ) self.sp1, self.sp2, self.sp3 = sponsorships baker.make_recipe("sponsors.tests.logo_at_download_feature", sponsor_benefit__sponsorship=self.sp1) baker.make_recipe("sponsors.tests.logo_at_sponsors_feature", sponsor_benefit__sponsorship=self.sp1) baker.make_recipe("sponsors.tests.logo_at_sponsors_feature", sponsor_benefit__sponsorship=self.sp2) - baker.make_recipe("sponsors.tests.logo_at_pypi_feature", sponsor_benefit__sponsorship=self.sp3, - link_to_sponsors_page=True, describe_as_sponsor=True) + baker.make_recipe( + "sponsors.tests.logo_at_pypi_feature", + sponsor_benefit__sponsorship=self.sp3, + link_to_sponsors_page=True, + describe_as_sponsor=True, + ) def tearDown(self): for sponsor in Sponsor.objects.all(): @@ -53,20 +58,19 @@ def test_list_logo_placement_as_expected(self): self.assertEqual(1, len([p for p in data if p["sponsor"] == self.sponsors[1].name])) self.assertEqual(1, len([p for p in data if p["sponsor"] == self.sponsors[2].name])) self.assertEqual( - None, - [p for p in data if p["publisher"] == PublisherChoices.FOUNDATION.value][0]['sponsor_url'] + None, [p for p in data if p["publisher"] == PublisherChoices.FOUNDATION.value][0]["sponsor_url"] ) self.assertEqual( f"http://testserver/psf/sponsors/#{slugify(self.sp3.sponsor.name)}", - [p for p in data if p["publisher"] == PublisherChoices.PYPI.value][0]['sponsor_url'] + [p for p in data if p["publisher"] == PublisherChoices.PYPI.value][0]["sponsor_url"], ) self.assertCountEqual( [self.sp1.sponsor.description, self.sp1.sponsor.description, self.sp2.sponsor.description], - [p['description'] for p in data if p["publisher"] == PublisherChoices.FOUNDATION.value] + [p["description"] for p in data if p["publisher"] == PublisherChoices.FOUNDATION.value], ) self.assertEqual( [f"{self.sp3.sponsor.name} is a {self.sp3.level_name} sponsor of the Python Software Foundation."], - [p['description'] for p in data if p["publisher"] == PublisherChoices.PYPI.value] + [p["description"] for p in data if p["publisher"] == PublisherChoices.PYPI.value], ) def test_invalid_token(self): @@ -95,9 +99,11 @@ def test_user_must_have_required_permission(self): self.assertEqual(403, response.status_code) def test_filter_sponsorship_by_publisher(self): - querystring = urlencode({ - "publisher": PublisherChoices.PYPI.value, - }) + querystring = urlencode( + { + "publisher": PublisherChoices.PYPI.value, + } + ) url = f"{self.url}?{querystring}" response = self.client.get(url, headers={"authorization": self.authorization}) data = response.json() @@ -107,9 +113,11 @@ def test_filter_sponsorship_by_publisher(self): self.assertEqual(self.sp3.sponsor.name, data[0]["sponsor"]) def test_filter_sponsorship_by_flight(self): - querystring = urlencode({ - "flight": LogoPlacementChoices.SIDEBAR.value, - }) + querystring = urlencode( + { + "flight": LogoPlacementChoices.SIDEBAR.value, + } + ) url = f"{self.url}?{querystring}" response = self.client.get(url, headers={"authorization": self.authorization}) data = response.json() @@ -120,10 +128,7 @@ def test_filter_sponsorship_by_flight(self): self.assertEqual(self.sp3.sponsor.slug, data[0]["sponsor_slug"]) def test_bad_request_for_invalid_filters(self): - querystring = urlencode({ - "flight": "invalid-flight", - "publisher": "invalid-publisher" - }) + querystring = urlencode({"flight": "invalid-flight", "publisher": "invalid-publisher"}) url = f"{self.url}?{querystring}" response = self.client.get(url, headers={"authorization": self.authorization}) data = response.json() @@ -134,17 +139,16 @@ def test_bad_request_for_invalid_filters(self): class SponsorshipAssetsAPIListTests(APITestCase): - def setUp(self): - self.user = baker.make('users.User') + self.user = baker.make("users.User") token = Token.objects.get(user=self.user) - self.permission = Permission.objects.get(name='Can access sponsor placement API') + self.permission = Permission.objects.get(name="Can access sponsor placement API") self.user.user_permissions.add(self.permission) - self.authorization = f'Token {token.key}' + self.authorization = f"Token {token.key}" self.internal_name = "txt_assets" self.url = reverse_lazy("assets_list") + f"?internal_name={self.internal_name}" - self.sponsorship = baker.make(Sponsorship, sponsor__name='Sponsor 1') - self.sponsor = baker.make(Sponsor, name='Sponsor 2') + self.sponsorship = baker.make(Sponsorship, sponsor__name="Sponsor 1") + self.sponsor = baker.make(Sponsor, name="Sponsor 2") self.txt_asset = TextAsset.objects.create( internal_name=self.internal_name, uuid=uuid.uuid4(), @@ -226,7 +230,7 @@ def test_enable_to_filter_by_assets_with_no_value_via_querystring(self): self.assertEqual(data[0]["sponsor_slug"], "sponsor-1") def test_serialize_img_value_as_url_to_image(self): - self.img_asset.value = SimpleUploadedFile(name='test_image.jpg', content=b"content", content_type='image/jpeg') + self.img_asset.value = SimpleUploadedFile(name="test_image.jpg", content=b"content", content_type="image/jpeg") self.img_asset.save() url = reverse_lazy("assets_list") + f"?internal_name={self.img_asset.internal_name}" diff --git a/sponsors/tests/test_contracts.py b/sponsors/tests/test_contracts.py index c330c13a8..bcbedfb6c 100644 --- a/sponsors/tests/test_contracts.py +++ b/sponsors/tests/test_contracts.py @@ -21,11 +21,9 @@ def test_render_response_with_docx_attachment(self): self.assertEqual(response.get("Content-Disposition"), "attachment; filename=sponsorship-contract-Sponsor.docx") self.assertEqual( - response.get("Content-Type"), - "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + response.get("Content-Type"), "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ) - # DOCX unit test def test_render_renewal_response_with_docx_attachment(self): request = Mock(HttpRequest) @@ -34,6 +32,5 @@ def test_render_renewal_response_with_docx_attachment(self): self.assertEqual(response.get("Content-Disposition"), "attachment; filename=sponsorship-renewal-Sponsor.docx") self.assertEqual( - response.get("Content-Type"), - "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + response.get("Content-Type"), "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ) diff --git a/sponsors/tests/test_forms.py b/sponsors/tests/test_forms.py index 49b0515cd..dd0c55d20 100644 --- a/sponsors/tests/test_forms.py +++ b/sponsors/tests/test_forms.py @@ -16,10 +16,21 @@ SponsorBenefit, Sponsorship, SponsorshipsListForm, - SendSponsorshipNotificationForm, SponsorRequiredAssetsForm, SponsorshipBenefitAdminForm, CloneApplicationConfigForm, + SendSponsorshipNotificationForm, + SponsorRequiredAssetsForm, + SponsorshipBenefitAdminForm, + CloneApplicationConfigForm, +) +from sponsors.models import ( + SponsorshipBenefit, + SponsorContact, + RequiredTextAssetConfiguration, + RequiredImgAssetConfiguration, + ImgAsset, + RequiredTextAsset, + SponsorshipPackage, + SponsorshipCurrentYear, ) -from sponsors.models import SponsorshipBenefit, SponsorContact, RequiredTextAssetConfiguration, \ - RequiredImgAssetConfiguration, ImgAsset, RequiredTextAsset, SponsorshipPackage, SponsorshipCurrentYear from .utils import get_static_image_file_as_upload from ..models.enums import AssetsRelatedTo @@ -29,22 +40,14 @@ def setUp(self): self.current_year = SponsorshipCurrentYear.get_year() self.psf = baker.make("sponsors.SponsorshipProgram", name="PSF") self.wk = baker.make("sponsors.SponsorshipProgram", name="Working Group") - self.program_1_benefits = baker.make( - SponsorshipBenefit, program=self.psf, _quantity=3, year=self.current_year - ) - self.program_2_benefits = baker.make( - SponsorshipBenefit, program=self.wk, _quantity=5, year=self.current_year - ) - self.package = baker.make( - "sponsors.SponsorshipPackage", advertise=True, year=self.current_year - ) + self.program_1_benefits = baker.make(SponsorshipBenefit, program=self.psf, _quantity=3, year=self.current_year) + self.program_2_benefits = baker.make(SponsorshipBenefit, program=self.wk, _quantity=5, year=self.current_year) + self.package = baker.make("sponsors.SponsorshipPackage", advertise=True, year=self.current_year) self.package.benefits.add(*self.program_1_benefits) self.package.benefits.add(*self.program_2_benefits) # packages without associated packages - self.a_la_carte = baker.make( - SponsorshipBenefit, program=self.psf, _quantity=2, year=self.current_year - ) + self.a_la_carte = baker.make(SponsorshipBenefit, program=self.psf, _quantity=2, year=self.current_year) # standalone benefits self.standalone = baker.make( @@ -53,9 +56,7 @@ def setUp(self): def test_specific_field_to_select_a_la_carte_by_year(self): prev_year = self.current_year - 1 - from_prev_year = baker.make( - SponsorshipBenefit, program=self.psf, _quantity=2, year=prev_year - ) + from_prev_year = baker.make(SponsorshipBenefit, program=self.psf, _quantity=2, year=prev_year) # current year by default form = SponsorshipsBenefitsForm() choices = list(form.fields["a_la_carte_benefits"].choices) @@ -70,12 +71,8 @@ def test_specific_field_to_select_a_la_carte_by_year(self): self.assertIn(benefit.id, [c[0] for c in choices]) def test_benefits_from_current_year_organized_by_program(self): - older_psf = baker.make( - SponsorshipBenefit, program=self.psf, _quantity=3, year=self.current_year - 1 - ) - older_wk = baker.make( - SponsorshipBenefit, program=self.wk, _quantity=5, year=self.current_year - 1 - ) + older_psf = baker.make(SponsorshipBenefit, program=self.psf, _quantity=3, year=self.current_year - 1) + older_wk = baker.make(SponsorshipBenefit, program=self.wk, _quantity=5, year=self.current_year - 1) self.package.benefits.add(*older_psf) self.package.benefits.add(*older_wk) @@ -99,9 +96,7 @@ def test_benefits_from_current_year_organized_by_program(self): def test_specific_field_to_select_standalone_benefits_by_year(self): prev_year = self.current_year - 1 # standalone benefits - prev_benefits = baker.make( - SponsorshipBenefit, program=self.psf, standalone=True, _quantity=2, year=prev_year - ) + prev_benefits = baker.make(SponsorshipBenefit, program=self.psf, standalone=True, _quantity=2, year=prev_year) # Current year by default form = SponsorshipsBenefitsForm() @@ -118,11 +113,9 @@ def test_specific_field_to_select_standalone_benefits_by_year(self): self.assertIn(benefit.id, [c[0] for c in choices]) def test_package_list_only_advertisable_ones_from_current_year(self): - ads_pkgs = baker.make( - 'SponsorshipPackage', advertise=True, _quantity=2, year=self.current_year - ) - baker.make('SponsorshipPackage', advertise=False) - baker.make('SponsorshipPackage', advertise=False, year=self.current_year) + ads_pkgs = baker.make("SponsorshipPackage", advertise=True, _quantity=2, year=self.current_year) + baker.make("SponsorshipPackage", advertise=False) + baker.make("SponsorshipPackage", advertise=False, year=self.current_year) form = SponsorshipsBenefitsForm() field = form.fields.get("package") @@ -141,9 +134,7 @@ def test_invalidate_form_without_benefits(self): def test_validate_form_without_package_but_with_standalone_benefits(self): benefit = self.standalone[0] - form = SponsorshipsBenefitsForm( - data={"standalone_benefits": [benefit.id]} - ) + form = SponsorshipsBenefitsForm(data={"standalone_benefits": [benefit.id]}) self.assertTrue(form.is_valid()) self.assertEqual([], form.get_benefits()) self.assertEqual([benefit], form.get_benefits(include_standalone=True)) @@ -157,10 +148,7 @@ def test_do_not_validate_form_with_package_and_standalone_benefits(self): } form = SponsorshipsBenefitsForm(data=data) self.assertFalse(form.is_valid()) - self.assertIn( - "Application with package cannot have standalone benefits.", - form.errors["__all__"] - ) + self.assertIn("Application with package cannot have standalone benefits.", form.errors["__all__"]) def test_should_not_validate_form_without_package_with_a_la_carte_benefits(self): data = { @@ -170,14 +158,13 @@ def test_should_not_validate_form_without_package_with_a_la_carte_benefits(self) form = SponsorshipsBenefitsForm(data=data) self.assertFalse(form.is_valid()) - self.assertIn( - "You must pick a package to include the selected benefits.", - form.errors["__all__"] - ) + self.assertIn("You must pick a package to include the selected benefits.", form.errors["__all__"]) - data.update({ - "package": self.package.id, - }) + data.update( + { + "package": self.package.id, + } + ) form = SponsorshipsBenefitsForm(data=data) self.assertTrue(form.is_valid()) @@ -191,10 +178,7 @@ def test_do_not_validate_package_package_with_disabled_a_la_carte_benefits(self) } form = SponsorshipsBenefitsForm(data=data) self.assertFalse(form.is_valid()) - self.assertIn( - "Package does not accept a la carte benefits.", - form.errors["__all__"] - ) + self.assertIn("Package does not accept a la carte benefits.", form.errors["__all__"]) data.pop("a_la_carte_benefits") form = SponsorshipsBenefitsForm(data=data) self.assertTrue(form.is_valid(), form.errors) @@ -208,15 +192,9 @@ def test_benefits_conflicts_helper_property(self): map = form.benefits_conflicts # conflicts are symmetrical relationships - self.assertEqual( - 2 + len(self.program_1_benefits) + len(self.program_2_benefits), len(map) - ) - self.assertEqual( - sorted(map[benefit_1.id]), sorted(b.id for b in self.program_1_benefits) - ) - self.assertEqual( - sorted(map[benefit_2.id]), sorted(b.id for b in self.program_2_benefits) - ) + self.assertEqual(2 + len(self.program_1_benefits) + len(self.program_2_benefits), len(map)) + self.assertEqual(sorted(map[benefit_1.id]), sorted(b.id for b in self.program_1_benefits)) + self.assertEqual(sorted(map[benefit_2.id]), sorted(b.id for b in self.program_2_benefits)) for b in self.program_1_benefits: self.assertEqual(map[b.id], [benefit_1.id]) for b in self.program_2_benefits: @@ -242,9 +220,11 @@ def test_invalid_form_if_any_conflict(self): def test_get_benefits_from_cleaned_data(self): benefit = self.program_1_benefits[0] - data = {"benefits_psf": [benefit.id], - "a_la_carte_benefits": [b.id for b in self.a_la_carte], - "package": self.package.id} + data = { + "benefits_psf": [benefit.id], + "a_la_carte_benefits": [b.id for b in self.a_la_carte], + "package": self.package.id, + } form = SponsorshipsBenefitsForm(data=data) self.assertTrue(form.is_valid()) @@ -277,7 +257,9 @@ def test_package_only_benefit_with_wrong_package_should_not_validate(self): data = { "benefits_psf": [self.program_1_benefits[0]], - "package": baker.make("sponsors.SponsorshipPackage", advertise=True, year=self.current_year).id, # other package + "package": baker.make( + "sponsors.SponsorshipPackage", advertise=True, year=self.current_year + ).id, # other package } form = SponsorshipsBenefitsForm(data=data) @@ -363,9 +345,7 @@ def setUp(self): "contact-MIN_NUM_FORMS": 1, "contact-INITIAL_FORMS": 1, } - self.files = { - "web_logo": get_static_image_file_as_upload("psf-logo.png", "logo.png") - } + self.files = {"web_logo": get_static_image_file_as_upload("psf-logo.png", "logo.png")} def test_required_fields(self): required_fields = [ @@ -421,7 +401,7 @@ def test_create_sponsor_with_valid_data(self): self.assertIsNone(contact.user) def test_create_sponsor_with_valid_data_for_non_required_inputs( - self, + self, ): user = baker.make(settings.AUTH_USER_MODEL) @@ -430,9 +410,7 @@ def test_create_sponsor_with_valid_data_for_non_required_inputs( self.data["twitter_handle"] = "@companyx" self.data["country_of_incorporation"] = "US" self.data["state_of_incorporation"] = "NY" - self.files["print_logo"] = get_static_image_file_as_upload( - "psf-logo_print.png", "logo_print.png" - ) + self.files["print_logo"] = get_static_image_file_as_upload("psf-logo_print.png", "logo_print.png") form = SponsorshipApplicationForm(self.data, self.files, user=user) self.assertTrue(form.is_valid(), form.errors) @@ -448,9 +426,9 @@ def test_create_sponsor_with_valid_data_for_non_required_inputs( self.assertEqual(sponsor.state_of_incorporation, "NY") def test_create_sponsor_with_svg_for_print_logo( - self, + self, ): - tick_svg = Path(settings.STATICFILES_DIRS[0]) / "img"/"sponsors"/"tick.svg" + tick_svg = Path(settings.STATICFILES_DIRS[0]) / "img" / "sponsors" / "tick.svg" with tick_svg.open("rb") as fd: uploaded_svg = SimpleUploadedFile("tick.svg", fd.read()) self.files["print_logo"] = uploaded_svg @@ -660,12 +638,16 @@ def test_update_existing_benefit_features(self): sponsorship_benefit=self.benefit, ) # existing benefit depends on logo - baker.make_recipe('sponsors.tests.logo_at_download_feature', sponsor_benefit=sponsor_benefit) + baker.make_recipe("sponsors.tests.logo_at_download_feature", sponsor_benefit=sponsor_benefit) # new benefit requires text instead of logo new_benefit = baker.make(SponsorshipBenefit) - baker.make(RequiredTextAssetConfiguration, benefit=new_benefit, internal_name='foo', - related_to=AssetsRelatedTo.SPONSORSHIP.value) + baker.make( + RequiredTextAssetConfiguration, + benefit=new_benefit, + internal_name="foo", + related_to=AssetsRelatedTo.SPONSORSHIP.value, + ) self.data["sponsorship_benefit"] = new_benefit.pk form = SponsorBenefitAdminInlineForm(data=self.data, instance=sponsor_benefit) @@ -678,7 +660,6 @@ def test_update_existing_benefit_features(self): class SponsorshipsFormTestCase(TestCase): - def test_list_all_sponsorships_as_choices_by_default(self): sponsorships = baker.make(Sponsorship, _quantity=3) @@ -706,7 +687,6 @@ def test_init_form_from_sponsorship_benefit(self): class SponsorContactFormTests(TestCase): - def test_ensure_model_form_configuration(self): expected_fields = ["name", "email", "phone", "primary", "administrative", "accounting"] meta = SponsorContactForm._meta @@ -715,7 +695,6 @@ def test_ensure_model_form_configuration(self): class SendSponsorshipNotificationFormTests(TestCase): - def setUp(self): self.notification = baker.make("sponsors.SponsorEmailNotificationTemplate") self.data = { @@ -759,7 +738,6 @@ def test_validate_form_with_custom_content(self): class SponsorRequiredAssetsFormTest(TestCase): - def setUp(self): self.sponsorship = baker.make(Sponsorship, sponsor__name="foo") self.required_text_cfg = baker.make( @@ -774,9 +752,7 @@ def setUp(self): internal_name="Image Input", _fill_optional=True, ) - self.benefits = baker.make( - SponsorBenefit, sponsorship=self.sponsorship, _quantity=3 - ) + self.benefits = baker.make(SponsorBenefit, sponsorship=self.sponsorship, _quantity=3) def test_build_form_with_no_fields_if_no_required_asset(self): form = SponsorRequiredAssetsForm(instance=self.sponsorship) @@ -846,7 +822,6 @@ def test_raise_error_if_form_initialized_without_instance(self): class SponsorshipBenefitAdminFormTests(TestCase): - def setUp(self): self.program = baker.make("sponsors.SponsorshipProgram") @@ -869,7 +844,6 @@ def test_standalone_benefit_cannot_have_package(self): class CloneApplicationConfigFormTests(TestCase): - def setUp(self): baker.make(SponsorshipBenefit, year=2022) baker.make(SponsorshipPackage, year=2023) diff --git a/sponsors/tests/test_management_command.py b/sponsors/tests/test_management_command.py index 100daad2a..92136a2e6 100644 --- a/sponsors/tests/test_management_command.py +++ b/sponsors/tests/test_management_command.py @@ -21,12 +21,8 @@ class CreatePyConVouchersForSponsorsTestCase(TestCase): def test_generate_voucher_codes(self, mock_api_call): for benefit_id, code in BENEFITS.items(): sponsor = baker.make("sponsors.Sponsor", name="Foo") - sponsorship = baker.make( - "sponsors.Sponsorship", status="finalized", sponsor=sponsor - ) - sponsorship_benefit = baker.make( - "sponsors.SponsorshipBenefit", id=benefit_id - ) + sponsorship = baker.make("sponsors.Sponsorship", status="finalized", sponsor=sponsor) + sponsorship_benefit = baker.make("sponsors.SponsorshipBenefit", id=benefit_id) sponsor_benefit = baker.make( "sponsors.SponsorBenefit", id=benefit_id, @@ -48,7 +44,5 @@ def test_generate_voucher_codes(self, mock_api_call): generate_voucher_codes(2020) for benefit_id, code in BENEFITS.items(): - asset = ProvidedTextAsset.objects.get( - sponsor_benefit__id=benefit_id, internal_name=code["internal_name"] - ) + asset = ProvidedTextAsset.objects.get(sponsor_benefit__id=benefit_id, internal_name=code["internal_name"]) self.assertEqual(asset.value, "test-promo-code") diff --git a/sponsors/tests/test_managers.py b/sponsors/tests/test_managers.py index c908cfc41..b55c2500a 100644 --- a/sponsors/tests/test_managers.py +++ b/sponsors/tests/test_managers.py @@ -4,16 +4,25 @@ from django.conf import settings from django.test import TestCase -from ..models import Sponsorship, SponsorBenefit, LogoPlacement, TieredBenefit, RequiredTextAsset, RequiredImgAsset, \ - BenefitFeature, SponsorshipPackage, SponsorshipBenefit, SponsorshipCurrentYear +from ..models import ( + Sponsorship, + SponsorBenefit, + LogoPlacement, + TieredBenefit, + RequiredTextAsset, + RequiredImgAsset, + BenefitFeature, + SponsorshipPackage, + SponsorshipBenefit, + SponsorshipCurrentYear, +) from sponsors.models.enums import LogoPlacementChoices, PublisherChoices class SponsorshipQuerySetTests(TestCase): - def setUp(self): self.user = baker.make(settings.AUTH_USER_MODEL) - self.contact = baker.make('sponsors.SponsorContact', user=self.user) + self.contact = baker.make("sponsors.SponsorContact", user=self.user) def test_visible_to_user(self): visible = [ @@ -45,23 +54,12 @@ def test_enabled_sponsorships(self): end_date=today + two_days, ) # group of still disabled sponsorships + baker.make(Sponsorship, status=Sponsorship.APPLIED, start_date=today - two_days, end_date=today + two_days) baker.make( - Sponsorship, - status=Sponsorship.APPLIED, - start_date=today - two_days, - end_date=today + two_days + Sponsorship, status=Sponsorship.FINALIZED, start_date=today + two_days, end_date=today + 2 * two_days ) baker.make( - Sponsorship, - status=Sponsorship.FINALIZED, - start_date=today + two_days, - end_date=today + 2 * two_days - ) - baker.make( - Sponsorship, - status=Sponsorship.FINALIZED, - start_date=today - 2 * two_days, - end_date=today - two_days + Sponsorship, status=Sponsorship.FINALIZED, start_date=today - 2 * two_days, end_date=today - two_days ) # shouldn't list overlapped sponsorships baker.make( @@ -78,14 +76,14 @@ def test_enabled_sponsorships(self): self.assertIn(enabled, qs) def test_filter_sponsorship_with_logo_placement_benefits(self): - sponsorship_with_download_logo = baker.make_recipe('sponsors.tests.finalized_sponsorship') - sponsorship_with_sponsors_logo = baker.make_recipe('sponsors.tests.finalized_sponsorship') - simple_sponsorship = baker.make_recipe('sponsors.tests.finalized_sponsorship') + sponsorship_with_download_logo = baker.make_recipe("sponsors.tests.finalized_sponsorship") + sponsorship_with_sponsors_logo = baker.make_recipe("sponsors.tests.finalized_sponsorship") + simple_sponsorship = baker.make_recipe("sponsors.tests.finalized_sponsorship") download_logo_benefit = baker.make(SponsorBenefit, sponsorship=sponsorship_with_download_logo) - baker.make_recipe('sponsors.tests.logo_at_download_feature', sponsor_benefit=download_logo_benefit) + baker.make_recipe("sponsors.tests.logo_at_download_feature", sponsor_benefit=download_logo_benefit) sponsors_logo_benefit = baker.make(SponsorBenefit, sponsorship=sponsorship_with_sponsors_logo) - baker.make_recipe('sponsors.tests.logo_at_sponsors_feature', sponsor_benefit=sponsors_logo_benefit) + baker.make_recipe("sponsors.tests.logo_at_sponsors_feature", sponsor_benefit=sponsors_logo_benefit) regular_benefit = baker.make(SponsorBenefit, sponsorship=simple_sponsorship) with self.assertNumQueries(1): @@ -106,8 +104,8 @@ def test_filter_sponsorship_with_logo_placement_benefits(self): self.assertIn(sponsorship_with_download_logo, qs) def test_filter_sponsorship_by_benefit_feature_type(self): - sponsorship_feature_1 = baker.make_recipe('sponsors.tests.finalized_sponsorship') - sponsorship_feature_2 = baker.make_recipe('sponsors.tests.finalized_sponsorship') + sponsorship_feature_1 = baker.make_recipe("sponsors.tests.finalized_sponsorship") + sponsorship_feature_2 = baker.make_recipe("sponsors.tests.finalized_sponsorship") baker.make(LogoPlacement, sponsor_benefit__sponsorship=sponsorship_feature_1) baker.make(TieredBenefit, sponsor_benefit__sponsorship=sponsorship_feature_2) @@ -146,7 +144,6 @@ def test_filter_only_for_required_assets(self): class SponsorshipBenefitManagerTests(TestCase): - def setUp(self): package = baker.make(SponsorshipPackage) current_year = SponsorshipCurrentYear.get_year() @@ -154,8 +151,8 @@ def setUp(self): self.regular_benefit_unavailable = baker.make(SponsorshipBenefit, year=current_year, unavailable=True) self.regular_benefit.packages.add(package) self.regular_benefit.packages.add(package) - self.a_la_carte = baker.make(SponsorshipBenefit, year=current_year-1) - self.a_la_carte_unavail = baker.make(SponsorshipBenefit, year=current_year-1, unavailable=True) + self.a_la_carte = baker.make(SponsorshipBenefit, year=current_year - 1) + self.a_la_carte_unavail = baker.make(SponsorshipBenefit, year=current_year - 1, unavailable=True) self.standalone = baker.make(SponsorshipBenefit, standalone=True) self.standalone_unavail = baker.make(SponsorshipBenefit, standalone=True, unavailable=True) @@ -176,7 +173,6 @@ def test_filter_benefits_by_current_year(self): class SponsorshipPackageManagerTests(TestCase): - def test_filter_packages_by_current_year(self): current_year = SponsorshipCurrentYear.get_year() active_package = baker.make(SponsorshipPackage, year=current_year) diff --git a/sponsors/tests/test_models.py b/sponsors/tests/test_models.py index 3566f0b08..de9ebe3ab 100644 --- a/sponsors/tests/test_models.py +++ b/sponsors/tests/test_models.py @@ -23,8 +23,14 @@ SponsorshipBenefit, SponsorshipPackage, TieredBenefit, - TieredBenefitConfiguration, RequiredImgAssetConfiguration, RequiredImgAsset, ImgAsset, - RequiredTextAssetConfiguration, RequiredTextAsset, TextAsset, SponsorshipCurrentYear + TieredBenefitConfiguration, + RequiredImgAssetConfiguration, + RequiredImgAsset, + ImgAsset, + RequiredTextAssetConfiguration, + RequiredTextAsset, + TextAsset, + SponsorshipCurrentYear, ) from ..exceptions import ( SponsorWithExistingApplicationException, @@ -32,8 +38,13 @@ InvalidStatusException, ) from sponsors.models.enums import PublisherChoices, LogoPlacementChoices, AssetsRelatedTo -from ..models.benefits import RequiredAssetMixin, BaseRequiredImgAsset, BenefitFeature, BaseRequiredTextAsset, \ - EmailTargetableConfiguration +from ..models.benefits import ( + RequiredAssetMixin, + BaseRequiredImgAsset, + BenefitFeature, + BaseRequiredTextAsset, + EmailTargetableConfiguration, +) class SponsorshipBenefitModelTests(TestCase): @@ -73,13 +84,8 @@ def test_list_related_sponsorships(self): self.assertIn(sponsor_benefit.sponsorship, sponsorships) def test_name_for_display_without_specifying_package(self): - benefit = baker.make(SponsorshipBenefit, name='Benefit') - benefit_config = baker.make( - TieredBenefitConfiguration, - package__name='Package', - benefit=benefit, - quantity=10 - ) + benefit = baker.make(SponsorshipBenefit, name="Benefit") + benefit_config = baker.make(TieredBenefitConfiguration, package__name="Package", benefit=benefit, quantity=10) expected_name = f"Benefit (10)" name = benefit.name_for_display(package=benefit_config.package) @@ -111,9 +117,7 @@ def test_control_sponsorship_next_status(self): self.assertEqual(sponsorship.next_status, exepcted) def test_create_new_sponsorship(self): - sponsorship = Sponsorship.new( - self.sponsor, self.benefits, submited_by=self.user - ) + sponsorship = Sponsorship.new(self.sponsor, self.benefits, submited_by=self.user) self.assertTrue(sponsorship.pk) sponsorship.refresh_from_db() current_year = SponsorshipCurrentYear.get_year() @@ -141,9 +145,7 @@ def test_create_new_sponsorship(self): self.assertEqual(sponsor_benefit.name, benefit.name) self.assertEqual(sponsor_benefit.description, benefit.description) self.assertEqual(sponsor_benefit.program, benefit.program) - self.assertEqual( - sponsor_benefit.benefit_internal_value, benefit.internal_value - ) + self.assertEqual(sponsor_benefit.benefit_internal_value, benefit.internal_value) def test_create_new_sponsorship_with_package(self): sponsorship = Sponsorship.new(self.sponsor, self.benefits, package=self.package) @@ -248,7 +250,7 @@ def test_rollback_approved_sponsorship_with_contract_should_delete_it(self): sponsorship = Sponsorship.new(self.sponsor, self.benefits) sponsorship.status = Sponsorship.APPROVED sponsorship.save() - baker.make_recipe('sponsors.tests.empty_contract', sponsorship=sponsorship) + baker.make_recipe("sponsors.tests.empty_contract", sponsorship=sponsorship) sponsorship.rollback_to_editing() sponsorship.save() @@ -261,7 +263,7 @@ def test_can_not_rollback_sponsorship_to_edit_if_contract_was_sent(self): sponsorship = Sponsorship.new(self.sponsor, self.benefits) sponsorship.status = Sponsorship.APPROVED sponsorship.save() - baker.make_recipe('sponsors.tests.awaiting_signature_contract', sponsorship=sponsorship) + baker.make_recipe("sponsors.tests.awaiting_signature_contract", sponsorship=sponsorship) with self.assertRaises(InvalidStatusException): sponsorship.rollback_to_editing() @@ -302,7 +304,6 @@ def test_display_agreed_fee_for_approved_and_finalized_status(self): class SponsorshipCurrentYearTests(TestCase): - def test_singleton_object_is_loaded_by_default(self): curr_year = SponsorshipCurrentYear.objects.get() self.assertEqual(1, curr_year.pk) @@ -385,9 +386,7 @@ def test_no_user_customization_if_at_least_one_of_conflicts_is_passed(self): benefits[1].conflicts.add(benefits[2]) self.package.benefits.add(*benefits) - customization = self.package.has_user_customization( - self.package_benefits + benefits[:1] - ) + customization = self.package.has_user_customization(self.package_benefits + benefits[:1]) self.assertFalse(customization) def test_user_customization_if_missing_benefit_with_conflict(self): @@ -409,9 +408,7 @@ def test_user_customization_if_missing_benefit_with_conflict_from_one_or_more_co benefits[2].conflicts.add(benefits[3]) self.package.benefits.add(*benefits) - benefits = self.package_benefits + [ - benefits[0] - ] # missing benefits with index 2 or 3 + benefits = self.package_benefits + [benefits[0]] # missing benefits with index 2 or 3 customization = self.package.has_user_customization(benefits) self.assertTrue(customization) @@ -462,9 +459,7 @@ def test_get_primary_contact_for_sponsor(self): self.assertIsNone(sponsor.primary_contact) primary_contact = baker.make(SponsorContact, primary=True, sponsor=sponsor) - self.assertEqual( - SponsorContact.objects.get_primary_contact(sponsor), primary_contact - ) + self.assertEqual(SponsorContact.objects.get_primary_contact(sponsor), primary_contact) self.assertEqual(sponsor.primary_contact, primary_contact) @@ -524,9 +519,7 @@ def test_create_new_contract_from_sponsorship_sets_sponsor_contact_and_primary( self, ): sponsor = self.sponsorship.sponsor - contact = baker.make( - SponsorContact, sponsor=self.sponsorship.sponsor, primary=True - ) + contact = baker.make(SponsorContact, sponsor=self.sponsorship.sponsor, primary=True) contract = Contract.new(self.sponsorship) expected_contact = f"{contact.name} - {contact.phone} | {contact.email}" @@ -558,9 +551,7 @@ def test_format_benefits_with_legal_clauses(self): clause = legal_clauses[i] benefit.legal_clauses.add(clause) SponsorBenefit.new_copy(benefit, sponsorship=self.sponsorship) - self.sponsorship_benefits.first().legal_clauses.add( - clause - ) # first benefit with 2 legal clauses + self.sponsorship_benefits.first().legal_clauses.add(clause) # first benefit with 2 legal clauses contract = Contract.new(self.sponsorship) @@ -597,9 +588,7 @@ def test_control_contract_next_status(self): self.assertEqual(contract.next_status, exepcted) def test_set_final_document_version(self): - contract = baker.make_recipe( - "sponsors.tests.empty_contract", sponsorship__sponsor__name="foo" - ) + contract = baker.make_recipe("sponsors.tests.empty_contract", sponsorship__sponsor__name="foo") content = b"pdf binary content" self.assertFalse(contract.document.name) @@ -610,9 +599,7 @@ def test_set_final_document_version(self): self.assertEqual(contract.status, Contract.AWAITING_SIGNATURE) def test_set_final_document_version_saves_docx_document_too(self): - contract = baker.make_recipe( - "sponsors.tests.empty_contract", sponsorship__sponsor__name="foo" - ) + contract = baker.make_recipe("sponsors.tests.empty_contract", sponsorship__sponsor__name="foo") content = b"pdf binary content" docx_content = b"pdf binary content" @@ -623,17 +610,13 @@ def test_set_final_document_version_saves_docx_document_too(self): self.assertEqual(contract.status, Contract.AWAITING_SIGNATURE) def test_raise_invalid_status_exception_if_not_draft(self): - contract = baker.make_recipe( - "sponsors.tests.empty_contract", status=Contract.AWAITING_SIGNATURE - ) + contract = baker.make_recipe("sponsors.tests.empty_contract", status=Contract.AWAITING_SIGNATURE) with self.assertRaises(InvalidStatusException): contract.set_final_version(b"content") def test_execute_contract(self): - contract = baker.make_recipe( - "sponsors.tests.empty_contract", status=Contract.AWAITING_SIGNATURE - ) + contract = baker.make_recipe("sponsors.tests.empty_contract", status=Contract.AWAITING_SIGNATURE) contract.execute() contract.refresh_from_db() @@ -643,17 +626,13 @@ def test_execute_contract(self): self.assertEqual(contract.sponsorship.finalized_on, date.today()) def test_raise_invalid_status_when_trying_to_execute_contract_if_not_awaiting_signature(self): - contract = baker.make_recipe( - "sponsors.tests.empty_contract", status=Contract.OUTDATED - ) + contract = baker.make_recipe("sponsors.tests.empty_contract", status=Contract.OUTDATED) with self.assertRaises(InvalidStatusException): contract.execute() def test_nullify_contract(self): - contract = baker.make_recipe( - "sponsors.tests.empty_contract", status=Contract.AWAITING_SIGNATURE - ) + contract = baker.make_recipe("sponsors.tests.empty_contract", status=Contract.AWAITING_SIGNATURE) contract.nullify() contract.refresh_from_db() @@ -661,27 +640,22 @@ def test_nullify_contract(self): self.assertEqual(contract.status, Contract.NULLIFIED) def test_raise_invalid_status_when_trying_to_nullify_contract_if_not_awaiting_signature(self): - contract = baker.make_recipe( - "sponsors.tests.empty_contract", status=Contract.DRAFT - ) + contract = baker.make_recipe("sponsors.tests.empty_contract", status=Contract.DRAFT) with self.assertRaises(InvalidStatusException): contract.nullify() class SponsorBenefitModelTests(TestCase): - def setUp(self): self.sponsorship = baker.make(Sponsorship) - self.sponsorship_benefit = baker.make(SponsorshipBenefit, name='Benefit') + self.sponsorship_benefit = baker.make(SponsorshipBenefit, name="Benefit") def test_new_copy_also_add_benefit_feature_when_creating_sponsor_benefit(self): benefit_config = baker.make(LogoPlacementConfiguration, benefit=self.sponsorship_benefit) self.assertEqual(0, LogoPlacement.objects.count()) - sponsor_benefit = SponsorBenefit.new_copy( - self.sponsorship_benefit, sponsorship=self.sponsorship - ) + sponsor_benefit = SponsorBenefit.new_copy(self.sponsorship_benefit, sponsorship=self.sponsorship) self.assertEqual(1, LogoPlacement.objects.count()) benefit_feature = sponsor_benefit.features.get() @@ -692,14 +666,12 @@ def test_new_copy_also_add_benefit_feature_when_creating_sponsor_benefit(self): def test_new_copy_do_not_save_unexisting_features(self): benefit_config = baker.make( TieredBenefitConfiguration, - package__name='Another package', + package__name="Another package", benefit=self.sponsorship_benefit, ) self.assertEqual(0, TieredBenefit.objects.count()) - sponsor_benefit = SponsorBenefit.new_copy( - self.sponsorship_benefit, sponsorship=self.sponsorship - ) + sponsor_benefit = SponsorBenefit.new_copy(self.sponsorship_benefit, sponsorship=self.sponsorship) self.assertEqual(0, TieredBenefit.objects.count()) self.assertFalse(sponsor_benefit.features.exists()) @@ -710,27 +682,19 @@ def test_sponsor_benefit_name_for_display(self): # benefit name if no features self.assertEqual(sponsor_benefit.name_for_display, name) # apply display modifier from features - benefit_config = baker.make( - TieredBenefit, - sponsor_benefit=sponsor_benefit, - quantity=10 - ) + benefit_config = baker.make(TieredBenefit, sponsor_benefit=sponsor_benefit, quantity=10) self.assertEqual(sponsor_benefit.name_for_display, f"{name} (10)") def test_sponsor_benefit_from_standalone_one(self): self.sponsorship_benefit.standalone = True self.sponsorship_benefit.save() - sponsor_benefit = SponsorBenefit.new_copy( - self.sponsorship_benefit, sponsorship=self.sponsorship - ) + sponsor_benefit = SponsorBenefit.new_copy(self.sponsorship_benefit, sponsorship=self.sponsorship) self.assertTrue(sponsor_benefit.added_by_user) self.assertTrue(sponsor_benefit.standalone) def test_reset_attributes_updates_all_basic_information(self): - benefit = baker.make( - SponsorBenefit, sponsorship_benefit=self.sponsorship_benefit - ) + benefit = baker.make(SponsorBenefit, sponsorship_benefit=self.sponsorship_benefit) # both have different random values self.assertNotEqual(benefit.name, self.sponsorship_benefit.name) @@ -751,9 +715,7 @@ def test_reset_attributes_add_new_features(self): internal_name="foo", label="Text", ) - benefit = baker.make( - SponsorBenefit, sponsorship_benefit=self.sponsorship_benefit - ) + benefit = baker.make(SponsorBenefit, sponsorship_benefit=self.sponsorship_benefit) # no previous feature self.assertFalse(benefit.features.count()) @@ -769,9 +731,7 @@ def test_reset_attributes_delete_removed_features(self): internal_name="foo", label="Text", ) - benefit = SponsorBenefit.new_copy( - self.sponsorship_benefit, sponsorship=self.sponsorship - ) + benefit = SponsorBenefit.new_copy(self.sponsorship_benefit, sponsorship=self.sponsorship) self.assertEqual(1, benefit.features.count()) cfg.delete() @@ -788,9 +748,7 @@ def test_reset_attributes_recreate_features_but_keeping_previous_values(self): internal_name="foo", label="Text", ) - benefit = SponsorBenefit.new_copy( - self.sponsorship_benefit, sponsorship=self.sponsorship - ) + benefit = SponsorBenefit.new_copy(self.sponsorship_benefit, sponsorship=self.sponsorship) feature = RequiredTextAsset.objects.get() feature.value = "foo" @@ -810,7 +768,7 @@ def test_reset_attributes_recreate_features_but_keeping_previous_values(self): def test_clone_benefit_regular_attributes_to_a_new_year(self): benefit = baker.make( SponsorshipBenefit, - name='Benefit', + name="Benefit", description="desc", program__name="prog", package_only=False, @@ -821,7 +779,7 @@ def test_clone_benefit_regular_attributes_to_a_new_year(self): internal_value=300, capacity=100, soft_capacity=True, - year=2022 + year=2022, ) benefit_2023, created = benefit.clone(year=2023) self.assertTrue(created) @@ -862,15 +820,15 @@ def test_clone_related_objects_as_well(self): def test_clone_benefit_feature_configurations(self): cfg_1 = baker.make( LogoPlacementConfiguration, - publisher = PublisherChoices.FOUNDATION, - logo_place = LogoPlacementChoices.FOOTER, - benefit=self.sponsorship_benefit + publisher=PublisherChoices.FOUNDATION, + logo_place=LogoPlacementChoices.FOOTER, + benefit=self.sponsorship_benefit, ) cfg_2 = baker.make( RequiredTextAssetConfiguration, related_to=AssetsRelatedTo.SPONSOR.value, internal_name="config_name", - benefit=self.sponsorship_benefit + benefit=self.sponsorship_benefit, ) benefit_2023, _ = self.sponsorship_benefit.clone(2023) @@ -882,7 +840,6 @@ def test_clone_benefit_feature_configurations(self): class LegalClauseTests(TestCase): - def test_clone_legal_clause(self): clause = baker.make(LegalClause) new_clause = clause.clone() @@ -895,17 +852,14 @@ def test_clone_legal_clause(self): ########### # Email notification tests class SponsorEmailNotificationTemplateTests(TestCase): - def setUp(self): self.notification = baker.make( - 'sponsors.SponsorEmailNotificationTemplate', + "sponsors.SponsorEmailNotificationTemplate", subject="Subject - {{ sponsor_name }}", content="Hi {{ sponsor_name }}, how are you?", ) self.sponsorship = baker.make(Sponsorship, sponsor__name="Foo") - self.contact = baker.make( - SponsorContact, sponsor=self.sponsorship.sponsor, primary=True - ) + self.contact = baker.make(SponsorContact, sponsor=self.sponsorship.sponsor, primary=True) def test_map_sponsorship_info_to_simplified_context_data(self): expected_context = { @@ -914,20 +868,16 @@ def test_map_sponsorship_info_to_simplified_context_data(self): "sponsorship_end_date": self.sponsorship.end_date, "sponsorship_status": self.sponsorship.status, "sponsorship_level": self.sponsorship.level_name, - "extra": "foo" + "extra": "foo", } context = self.notification.get_email_context_data(sponsorship=self.sponsorship, extra="foo") self.assertEqual(expected_context, context) def test_get_email_message(self): - manager = baker.make( - SponsorContact, sponsor=self.sponsorship.sponsor, manager=True - ) + manager = baker.make(SponsorContact, sponsor=self.sponsorship.sponsor, manager=True) baker.make(SponsorContact, sponsor=self.sponsorship.sponsor, accounting=True) - email = self.notification.get_email_message( - self.sponsorship, to_primary=True, to_manager=True - ) + email = self.notification.get_email_message(self.sponsorship, to_primary=True, to_manager=True) self.assertIsInstance(email, EmailMessage) self.assertEqual("Subject - Foo", email.subject) @@ -951,7 +901,6 @@ def test_get_email_message_returns_none_if_no_contact(self): ####### Benefit features/configuration tests ########### class LogoPlacementConfigurationModelTests(TestCase): - def setUp(self): self.config = baker.make( LogoPlacementConfiguration, @@ -970,7 +919,7 @@ def test_get_benefit_feature_respecting_configuration(self): self.assertIsNone(benefit_feature.sponsor_benefit_id) def test_display_modifier_returns_same_name(self): - name = 'Benefit' + name = "Benefit" self.assertEqual(name, self.config.display_modifier(name)) def test_clone_configuration_for_new_sponsorship_benefit(self): @@ -990,7 +939,6 @@ def test_clone_configuration_for_new_sponsorship_benefit(self): class TieredBenefitConfigurationModelTests(TestCase): - def setUp(self): self.package = baker.make(SponsorshipPackage, year=2022) self.config = baker.make( @@ -1011,7 +959,7 @@ def test_get_benefit_feature_respecting_configuration(self): self.assertEqual(benefit_feature.display_label, "Foo") def test_do_not_return_feature_if_benefit_from_other_package(self): - sponsor_benefit = baker.make(SponsorBenefit, sponsorship__package__name='Other') + sponsor_benefit = baker.make(SponsorBenefit, sponsorship__package__name="Other") benefit_feature = self.config.get_benefit_feature(sponsor_benefit=sponsor_benefit) @@ -1056,30 +1004,27 @@ def test_clone_tiered_quantity_configuration(self): class LogoPlacementTests(TestCase): - def test_display_modifier_does_not_change_the_name(self): placement = baker.make(LogoPlacement) - name = 'Benefit' + name = "Benefit" self.assertEqual(placement.display_modifier(name), name) class TieredBenefitTests(TestCase): - def test_display_modifier_adds_quantity_to_the_name(self): placement = baker.make(TieredBenefit, quantity=10) - name = 'Benefit' - self.assertEqual(placement.display_modifier(name), 'Benefit (10)') + name = "Benefit" + self.assertEqual(placement.display_modifier(name), "Benefit (10)") def test_display_modifier_adds_display_label_to_the_name(self): placement = baker.make(TieredBenefit, quantity=10, display_label="Foo") - name = 'Benefit' - self.assertEqual(placement.display_modifier(name), 'Benefit (Foo)') + name = "Benefit" + self.assertEqual(placement.display_modifier(name), "Benefit (Foo)") class RequiredImgAssetConfigurationTests(TestCase): - def setUp(self): - self.sponsor_benefit = baker.make(SponsorBenefit, sponsorship__sponsor__name='Foo') + self.sponsor_benefit = baker.make(SponsorBenefit, sponsorship__sponsor__name="Foo") self.config = baker.make( RequiredImgAssetConfiguration, related_to=AssetsRelatedTo.SPONSOR.value, @@ -1128,9 +1073,8 @@ def test_clone_configuration_for_new_sponsorship_benefit_without_due_date(self): class RequiredTextAssetConfigurationTests(TestCase): - def setUp(self): - self.sponsor_benefit = baker.make(SponsorBenefit, sponsorship__sponsor__name='Foo') + self.sponsor_benefit = baker.make(SponsorBenefit, sponsorship__sponsor__name="Foo") self.config = baker.make( RequiredTextAssetConfiguration, related_to=AssetsRelatedTo.SPONSOR.value, @@ -1197,9 +1141,8 @@ def test_clone_configuration_for_new_sponsorship_benefit_with_new_due_date(self) class RequiredTextAssetTests(TestCase): - def setUp(self): - self.sponsor_benefit = baker.make(SponsorBenefit, sponsorship__sponsor__name='Foo') + self.sponsor_benefit = baker.make(SponsorBenefit, sponsorship__sponsor__name="Foo") def test_get_value_from_sponsor_asset(self): config = baker.make( @@ -1270,7 +1213,6 @@ def test_build_form_field_from_input(self): class EmailTargetableConfigurationTest(TestCase): - def test_clone_configuration_for_new_sponsorship_benefit_with_new_due_date(self): config = baker.make(EmailTargetableConfiguration) benefit = baker.make(SponsorshipBenefit, year=2023) diff --git a/sponsors/tests/test_notifications.py b/sponsors/tests/test_notifications.py index 30151ede0..6f01f6b2a 100644 --- a/sponsors/tests/test_notifications.py +++ b/sponsors/tests/test_notifications.py @@ -55,9 +55,7 @@ def setUp(self): baker.make("sponsors.SponsorContact", email=self.unverified_email.email), ] self.sponsor = baker.make("sponsors.Sponsor", contacts=self.sponsor_contacts) - self.sponsorship = baker.make( - "sponsors.Sponsorship", sponsor=self.sponsor, submited_by=self.user - ) + self.sponsorship = baker.make("sponsors.Sponsorship", sponsor=self.sponsor, submited_by=self.user) self.subject_template = "sponsors/email/sponsor_new_application_subject.txt" self.content_template = "sponsors/email/sponsor_new_application.txt" @@ -78,12 +76,10 @@ def test_send_email_using_correct_templates(self): def test_send_email_to_correct_recipients(self): context = {"user": self.user, "sponsorship": self.sponsorship} expected_contacts = ["foo@foo.com", self.verified_email.email] - self.assertCountEqual( - expected_contacts, self.notification.get_recipient_list(context) - ) + self.assertCountEqual(expected_contacts, self.notification.get_recipient_list(context)) def test_list_required_assets_in_email_context(self): - cfg = baker.make(RequiredTextAssetConfiguration, internal_name='input') + cfg = baker.make(RequiredTextAssetConfiguration, internal_name="input") benefit = baker.make(SponsorBenefit, sponsorship=self.sponsorship) asset = cfg.create_benefit_feature(benefit) request = Mock() @@ -132,9 +128,7 @@ def setUp(self): _fill_optional=["rejected_on", "sponsor"], submited_by=self.user, ) - self.subject_template = ( - "sponsors/email/sponsor_rejected_sponsorship_subject.txt" - ) + self.subject_template = "sponsors/email/sponsor_rejected_sponsorship_subject.txt" self.content_template = "sponsors/email/sponsor_rejected_sponsorship.txt" def test_send_email_using_correct_templates(self): @@ -263,17 +257,14 @@ def test_attach_contract_docx_if_it_exists(self): class SponsorshipApprovalLoggerTests(TestCase): - def setUp(self): - self.request = RequestFactory().get('/') + self.request = RequestFactory().get("/") self.request.user = baker.make(settings.AUTH_USER_MODEL) - self.sponsorship = baker.make(Sponsorship, status=Sponsorship.APPROVED, sponsor__name='foo', _fill_optional=True) + self.sponsorship = baker.make( + Sponsorship, status=Sponsorship.APPROVED, sponsor__name="foo", _fill_optional=True + ) self.contract = baker.make_recipe("sponsors.tests.empty_contract", sponsorship=self.sponsorship) - self.kwargs = { - "request": self.request, - "sponsorship": self.sponsorship, - "contract": self.contract - } + self.kwargs = {"request": self.request, "sponsorship": self.sponsorship, "contract": self.contract} self.logger = notifications.SponsorshipApprovalLogger() def test_create_log_entry_for_change_operation_with_approval_message(self): @@ -299,11 +290,10 @@ def test_create_log_entry_for_change_operation_with_approval_message(self): class SentContractLoggerTests(TestCase): - def setUp(self): - self.request = RequestFactory().get('/') + self.request = RequestFactory().get("/") self.request.user = baker.make(settings.AUTH_USER_MODEL) - self.contract = baker.make_recipe('sponsors.tests.empty_contract') + self.contract = baker.make_recipe("sponsors.tests.empty_contract") self.kwargs = { "request": self.request, "contract": self.contract, @@ -325,11 +315,10 @@ def test_create_log_entry_for_change_operation_with_approval_message(self): class ExecutedContractLoggerTests(TestCase): - def setUp(self): - self.request = RequestFactory().get('/') + self.request = RequestFactory().get("/") self.request.user = baker.make(settings.AUTH_USER_MODEL) - self.contract = baker.make_recipe('sponsors.tests.empty_contract') + self.contract = baker.make_recipe("sponsors.tests.empty_contract") self.kwargs = { "request": self.request, "contract": self.contract, @@ -351,11 +340,10 @@ def test_create_log_entry_for_change_operation_with_approval_message(self): class ExecutedExistingContractLoggerTests(TestCase): - def setUp(self): - self.request = RequestFactory().get('/') + self.request = RequestFactory().get("/") self.request.user = baker.make(settings.AUTH_USER_MODEL) - self.contract = baker.make_recipe('sponsors.tests.empty_contract') + self.contract = baker.make_recipe("sponsors.tests.empty_contract") self.kwargs = { "request": self.request, "contract": self.contract, @@ -377,11 +365,10 @@ def test_create_log_entry_for_change_operation_with_approval_message(self): class NullifiedContractLoggerTests(TestCase): - def setUp(self): - self.request = RequestFactory().get('/') + self.request = RequestFactory().get("/") self.request.user = baker.make(settings.AUTH_USER_MODEL) - self.contract = baker.make_recipe('sponsors.tests.empty_contract') + self.contract = baker.make_recipe("sponsors.tests.empty_contract") self.kwargs = { "request": self.request, "contract": self.contract, @@ -403,12 +390,11 @@ def test_create_log_entry_for_change_operation_with_approval_message(self): class SendSponsorNotificationLoggerTests(TestCase): - def setUp(self): - self.request = RequestFactory().get('/') + self.request = RequestFactory().get("/") self.request.user = baker.make(settings.AUTH_USER_MODEL) - self.sponsorship = baker.make('sponsors.Sponsorship', sponsor__name="Sponsor") - self.notification = baker.make('sponsors.SponsorEmailNotificationTemplate', internal_name="Foo") + self.sponsorship = baker.make("sponsors.Sponsorship", sponsor__name="Sponsor") + self.notification = baker.make("sponsors.SponsorEmailNotificationTemplate", internal_name="Foo") self.kwargs = { "request": self.request, "notification": self.notification, @@ -448,9 +434,7 @@ def setUp(self): baker.make("sponsors.SponsorContact", email=self.unverified_email.email), ] self.sponsor = baker.make("sponsors.Sponsor", contacts=self.sponsor_contacts) - self.sponsorship = baker.make( - "sponsors.Sponsorship", sponsor=self.sponsor, submited_by=self.user - ) + self.sponsorship = baker.make("sponsors.Sponsorship", sponsor=self.sponsor, submited_by=self.user) self.subject_template = "sponsors/email/sponsor_expiring_assets_subject.txt" self.content_template = "sponsors/email/sponsor_expiring_assets.txt" @@ -471,12 +455,10 @@ def test_send_email_using_correct_templates(self): def test_send_email_to_correct_recipients(self): context = {"user": self.user, "sponsorship": self.sponsorship} expected_contacts = ["foo@foo.com", self.verified_email.email] - self.assertCountEqual( - expected_contacts, self.notification.get_recipient_list(context) - ) + self.assertCountEqual(expected_contacts, self.notification.get_recipient_list(context)) def test_list_required_assets_in_email_context(self): - cfg = baker.make(RequiredTextAssetConfiguration, internal_name='input') + cfg = baker.make(RequiredTextAssetConfiguration, internal_name="input") benefit = baker.make(SponsorBenefit, sponsorship=self.sponsorship) asset = cfg.create_benefit_feature(benefit) base_context = {"sponsorship": self.sponsorship, "due_date": date.today(), "days": 7} @@ -489,18 +471,12 @@ def test_list_required_assets_in_email_context(self): class ClonedResourceLoggerTests(TestCase): - def setUp(self): - self.request = RequestFactory().get('/') + self.request = RequestFactory().get("/") self.request.user = baker.make(settings.AUTH_USER_MODEL) self.logger = notifications.ClonedResourcesLogger() self.package = baker.make("sponsors.SponsorshipPackage", name="Foo") - self.kwargs = { - "request": self.request, - "resource": self.package, - "from_year": 2022, - "extra": "foo" - } + self.kwargs = {"request": self.request, "resource": self.package, "from_year": 2022, "extra": "foo"} def test_create_log_entry_for_cloned_resource(self): self.assertEqual(LogEntry.objects.count(), 0) diff --git a/sponsors/tests/test_templatetags.py b/sponsors/tests/test_templatetags.py index f891a6479..686951371 100644 --- a/sponsors/tests/test_templatetags.py +++ b/sponsors/tests/test_templatetags.py @@ -20,9 +20,7 @@ class FullSponsorshipTemplatetagTests(TestCase): def test_templatetag_context(self): - sponsorship = baker.make( - "sponsors.Sponsorship", for_modified_package=False, _fill_optional=True - ) + sponsorship = baker.make("sponsors.Sponsorship", for_modified_package=False, _fill_optional=True) context = full_sponsorship(sponsorship) expected = { "sponsorship": sponsorship, @@ -33,28 +31,20 @@ def test_templatetag_context(self): self.assertEqual(context, expected) def test_do_not_display_fee_if_modified_package(self): - sponsorship = baker.make( - "sponsors.Sponsorship", for_modified_package=True, _fill_optional=True - ) + sponsorship = baker.make("sponsors.Sponsorship", for_modified_package=True, _fill_optional=True) context = full_sponsorship(sponsorship) self.assertFalse(context["display_fee"]) def test_allows_to_overwrite_display_fee_flag(self): - sponsorship = baker.make( - "sponsors.Sponsorship", for_modified_package=True, _fill_optional=True - ) + sponsorship = baker.make("sponsors.Sponsorship", for_modified_package=True, _fill_optional=True) context = full_sponsorship(sponsorship, display_fee=True) self.assertTrue(context["display_fee"]) class ListSponsorsTemplateTag(TestCase): - def test_filter_sponsorship_with_logo_placement_benefits(self): - sponsorship = baker.make_recipe('sponsors.tests.finalized_sponsorship') - baker.make_recipe( - 'sponsors.tests.logo_at_download_feature', - sponsor_benefit__sponsorship=sponsorship - ) + sponsorship = baker.make_recipe("sponsors.tests.finalized_sponsorship") + baker.make_recipe("sponsors.tests.logo_at_download_feature", sponsor_benefit__sponsorship=sponsorship) context = list_sponsors("download") @@ -64,7 +54,6 @@ def test_filter_sponsorship_with_logo_placement_benefits(self): class BenefitQuantityForPackageTests(TestCase): - def setUp(self): self.benefit = baker.make(SponsorshipBenefit) self.package = baker.make("sponsors.SponsorshipPackage") @@ -95,8 +84,7 @@ def test_return_empty_string_if_mismatching_benefit_or_package(self): class BenefitNameForDisplayTests(TestCase): - - @patch.object(SponsorshipBenefit, 'name_for_display') + @patch.object(SponsorshipBenefit, "name_for_display") def test_display_name_for_display_from_benefit(self, mocked_name_for_display): mocked_name_for_display.return_value = "Modified name" benefit = baker.make(SponsorshipBenefit) diff --git a/sponsors/tests/test_use_cases.py b/sponsors/tests/test_use_cases.py index 3e5e5ad04..2b420c1f0 100644 --- a/sponsors/tests/test_use_cases.py +++ b/sponsors/tests/test_use_cases.py @@ -12,34 +12,34 @@ from sponsors import use_cases from sponsors.notifications import * -from sponsors.models import Sponsorship, Contract, SponsorEmailNotificationTemplate, Sponsor, SponsorshipBenefit, \ - SponsorshipPackage +from sponsors.models import ( + Sponsorship, + Contract, + SponsorEmailNotificationTemplate, + Sponsor, + SponsorshipBenefit, + SponsorshipPackage, +) class CreateSponsorshipApplicationUseCaseTests(TestCase): def setUp(self): self.notifications = [Mock(), Mock()] - self.use_case = use_cases.CreateSponsorshipApplicationUseCase( - self.notifications - ) + self.use_case = use_cases.CreateSponsorshipApplicationUseCase(self.notifications) self.user = baker.make(settings.AUTH_USER_MODEL) self.sponsor = baker.make("sponsors.Sponsor") self.benefits = baker.make("sponsors.SponsorshipBenefit", _quantity=5) self.package = baker.make("sponsors.SponsorshipPackage") def test_create_new_sponsorship_using_benefits_and_package(self): - sponsorship = self.use_case.execute( - self.user, self.sponsor, self.benefits, self.package - ) + sponsorship = self.use_case.execute(self.user, self.sponsor, self.benefits, self.package) self.assertTrue(sponsorship.pk) self.assertEqual(len(self.benefits), sponsorship.benefits.count()) self.assertTrue(sponsorship.level_name) def test_send_notifications_using_sponsorship(self): - sponsorship = self.use_case.execute( - self.user, self.sponsor, self.benefits, self.package - ) + sponsorship = self.use_case.execute(self.user, self.sponsor, self.benefits, self.package) for n in self.notifications: n.notify.assert_called_once_with(request=None, sponsorship=sponsorship) @@ -49,17 +49,13 @@ def test_build_use_case_with_correct_notifications(self): self.assertEqual(len(uc.notifications), 2) self.assertIsInstance(uc.notifications[0], AppliedSponsorshipNotificationToPSF) - self.assertIsInstance( - uc.notifications[1], AppliedSponsorshipNotificationToSponsors - ) + self.assertIsInstance(uc.notifications[1], AppliedSponsorshipNotificationToSponsors) class RejectSponsorshipApplicationUseCaseTests(TestCase): def setUp(self): self.notifications = [Mock(), Mock()] - self.use_case = use_cases.RejectSponsorshipApplicationUseCase( - self.notifications - ) + self.use_case = use_cases.RejectSponsorshipApplicationUseCase(self.notifications) self.user = baker.make(settings.AUTH_USER_MODEL) self.sponsorship = baker.make(Sponsorship) @@ -82,17 +78,13 @@ def test_build_use_case_with_correct_notifications(self): self.assertEqual(len(uc.notifications), 2) self.assertIsInstance(uc.notifications[0], RejectedSponsorshipNotificationToPSF) - self.assertIsInstance( - uc.notifications[1], RejectedSponsorshipNotificationToSponsors - ) + self.assertIsInstance(uc.notifications[1], RejectedSponsorshipNotificationToSponsors) class ApproveSponsorshipApplicationUseCaseTests(TestCase): def setUp(self): self.notifications = [Mock(), Mock()] - self.use_case = use_cases.ApproveSponsorshipApplicationUseCase( - self.notifications - ) + self.use_case = use_cases.ApproveSponsorshipApplicationUseCase(self.notifications) self.user = baker.make(settings.AUTH_USER_MODEL) self.sponsorship = baker.make(Sponsorship, _fill_optional="sponsor") self.package = baker.make("sponsors.SponsorshipPackage") @@ -120,7 +112,6 @@ def test_update_sponsorship_as_approved_and_create_contract(self): self.assertEqual(self.sponsorship.level_name, self.package.name) self.assertFalse(self.sponsorship.renewal) - def test_update_renewal_sponsorship_as_approved_and_create_contract(self): self.data.update({"renewal": True}) self.use_case.execute(self.sponsorship, **self.data) @@ -177,9 +168,7 @@ def test_build_use_case_with_default_notificationss(self): uc = use_cases.SendContractUseCase.build() self.assertEqual(len(uc.notifications), 2) self.assertIsInstance(uc.notifications[0], ContractNotificationToPSF) - self.assertIsInstance( - uc.notifications[1], SentContractLogger - ) + self.assertIsInstance(uc.notifications[1], SentContractLogger) class ExecuteContractUseCaseTests(TestCase): @@ -207,11 +196,10 @@ def test_execute_and_update_database_object(self): def test_build_use_case_with_default_notificationss(self): uc = use_cases.ExecuteContractUseCase.build() self.assertEqual(len(uc.notifications), 2) + self.assertIsInstance(uc.notifications[0], ExecutedContractLogger) self.assertIsInstance( - uc.notifications[0], ExecutedContractLogger - ) - self.assertIsInstance( - uc.notifications[1], RefreshSponsorshipsCache, + uc.notifications[1], + RefreshSponsorshipsCache, ) @@ -242,11 +230,10 @@ def test_execute_and_update_database_object(self): def test_build_use_case_with_default_notifications(self): uc = use_cases.ExecuteExistingContractUseCase.build() self.assertEqual(len(uc.notifications), 2) + self.assertIsInstance(uc.notifications[0], ExecutedExistingContractLogger) self.assertIsInstance( - uc.notifications[0], ExecutedExistingContractLogger - ) - self.assertIsInstance( - uc.notifications[1], RefreshSponsorshipsCache, + uc.notifications[1], + RefreshSponsorshipsCache, ) def test_execute_contract_flag_overlapping_sponsorships(self): @@ -322,11 +309,10 @@ def test_nullify_and_update_database_object(self): def test_build_use_case_with_default_notificationss(self): uc = use_cases.NullifyContractUseCase.build() self.assertEqual(len(uc.notifications), 2) + self.assertIsInstance(uc.notifications[0], NullifiedContractLogger) self.assertIsInstance( - uc.notifications[0], NullifiedContractLogger - ) - self.assertIsInstance( - uc.notifications[1], RefreshSponsorshipsCache, + uc.notifications[1], + RefreshSponsorshipsCache, ) @@ -338,38 +324,43 @@ def setUp(self): self.sponsorships = baker.make(Sponsorship, sponsor__name="Foo", _quantity=3) self.sponsorships = Sponsorship.objects.all() # to respect DB order - @patch.object(SponsorEmailNotificationTemplate, 'get_email_message') + @patch.object(SponsorEmailNotificationTemplate, "get_email_message") def test_send_notifications(self, mock_get_email_message): emails = [Mock(EmailMessage, autospec=True) for i in range(3)] mock_get_email_message.side_effect = emails contact_types = ["administrative"] - self.use_case.execute(self.notification, self.sponsorships, contact_types, request='request') + self.use_case.execute(self.notification, self.sponsorships, contact_types, request="request") self.assertEqual(mock_get_email_message.call_count, 3) self.assertEqual(self.notifications[0].notify.call_count, 3) for sponsorship in self.sponsorships: kwargs = dict(to_accounting=False, to_administrative=True, to_manager=False, to_primary=False) mock_get_email_message.assert_has_calls([call(sponsorship, **kwargs)]) - self.notifications[0].notify.assert_has_calls([ - call(notification=self.notification, sponsorship=sponsorship, contact_types=contact_types, request='request') - ]) + self.notifications[0].notify.assert_has_calls( + [ + call( + notification=self.notification, + sponsorship=sponsorship, + contact_types=contact_types, + request="request", + ) + ] + ) for email in emails: email.send.assert_called_once_with() - @patch.object(SponsorEmailNotificationTemplate, 'get_email_message', Mock(return_value=None)) + @patch.object(SponsorEmailNotificationTemplate, "get_email_message", Mock(return_value=None)) def test_skip_sponsorships_if_no_email_message(self): contact_types = ["administrative"] - self.use_case.execute(self.notification, self.sponsorships, contact_types, request='request') + self.use_case.execute(self.notification, self.sponsorships, contact_types, request="request") self.assertEqual(self.notifications[0].notify.call_count, 0) def test_build_use_case_with_default_notificationss(self): uc = use_cases.SendSponsorshipNotificationUseCase.build() self.assertEqual(len(uc.notifications), 1) - self.assertIsInstance( - uc.notifications[0], SendSponsorNotificationLogger - ) + self.assertIsInstance(uc.notifications[0], SendSponsorNotificationLogger) class CloneSponsorshipYearUseCaseTests(TestCase): @@ -407,6 +398,4 @@ def test_clone_package_and_benefits(self): def test_build_use_case_with_default_notificationss(self): uc = use_cases.CloneSponsorshipYearUseCase.build() self.assertEqual(len(uc.notifications), 1) - self.assertIsInstance( - uc.notifications[0], ClonedResourcesLogger - ) + self.assertIsInstance(uc.notifications[0], ClonedResourcesLogger) diff --git a/sponsors/tests/test_views.py b/sponsors/tests/test_views.py index 130eb443d..9ff15bcbf 100644 --- a/sponsors/tests/test_views.py +++ b/sponsors/tests/test_views.py @@ -15,8 +15,9 @@ Sponsor, SponsorshipBenefit, SponsorContact, - Sponsorship, SponsorshipCurrentYear, - SponsorshipPackage + Sponsorship, + SponsorshipCurrentYear, + SponsorshipPackage, ) from sponsors.forms import ( SponsorshipsBenefitsForm, @@ -31,19 +32,13 @@ def setUp(self): self.current_year = SponsorshipCurrentYear.get_year() self.psf = baker.make("sponsors.SponsorshipProgram", name="PSF") self.wk = baker.make("sponsors.SponsorshipProgram", name="Working Group") - self.program_1_benefits = baker.make( - SponsorshipBenefit, program=self.psf, _quantity=3, year=self.current_year - ) - self.program_2_benefits = baker.make( - SponsorshipBenefit, program=self.wk, _quantity=5, year=self.current_year - ) + self.program_1_benefits = baker.make(SponsorshipBenefit, program=self.psf, _quantity=3, year=self.current_year) + self.program_2_benefits = baker.make(SponsorshipBenefit, program=self.wk, _quantity=5, year=self.current_year) self.package = baker.make(SponsorshipPackage, advertise=True, year=self.current_year) self.package.benefits.add(*self.program_1_benefits) package_2 = baker.make(SponsorshipPackage, advertise=True, year=self.current_year) package_2.benefits.add(*self.program_2_benefits) - self.a_la_carte_benefits = baker.make( - SponsorshipBenefit, program=self.psf, _quantity=2, year=self.current_year - ) + self.a_la_carte_benefits = baker.make(SponsorshipBenefit, program=self.psf, _quantity=2, year=self.current_year) self.standalone_benefits = baker.make( SponsorshipBenefit, program=self.psf, _quantity=2, standalone=True, year=self.current_year ) @@ -98,9 +93,7 @@ def test_valid_post_redirect_user_to_next_form_step_and_save_info_in_cookies(sel response = self.client.post(self.url, data=self.data) self.assertRedirects(response, reverse("new_sponsorship_application")) - cookie_value = json.loads( - response.client.cookies["sponsorship_selected_benefits"].value - ) + cookie_value = json.loads(response.client.cookies["sponsorship_selected_benefits"].value) self.assertEqual(self.data, cookie_value) def test_populate_form_initial_with_values_from_cookie(self): @@ -114,18 +107,14 @@ def test_capacity_flag(self): self.assertEqual(False, r.context["capacities_met"]) def test_capacity_flag_when_needed(self): - at_capacity_benefit = baker.make( - SponsorshipBenefit, program=self.psf, capacity=0, soft_capacity=False - ) + at_capacity_benefit = baker.make(SponsorshipBenefit, program=self.psf, capacity=0, soft_capacity=False) psf_package = baker.make(SponsorshipPackage, advertise=True) r = self.client.get(self.url) self.assertEqual(True, r.context["capacities_met"]) def test_redirect_to_login(self): - redirect_url = ( - f"{settings.LOGIN_URL}?next={reverse('new_sponsorship_application')}" - ) + redirect_url = f"{settings.LOGIN_URL}?next={reverse('new_sponsorship_application')}" self.client.logout() self.populate_test_cookie() @@ -155,9 +144,7 @@ def test_valid_only_with_standalone(self): response = self.client.post(self.url, data=self.data) self.assertRedirects(response, reverse("new_sponsorship_application")) - cookie_value = json.loads( - response.client.cookies["sponsorship_selected_benefits"].value - ) + cookie_value = json.loads(response.client.cookies["sponsorship_selected_benefits"].value) self.assertEqual(self.data, cookie_value) def test_do_not_display_application_form_by_year_if_staff_user(self): @@ -197,14 +184,10 @@ class NewSponsorshipApplicationViewTests(TestCase): def setUp(self): self.current_year = SponsorshipCurrentYear.get_year() - self.user = baker.make( - settings.AUTH_USER_MODEL, is_staff=True, email="bernardo@companyemail.com" - ) + self.user = baker.make(settings.AUTH_USER_MODEL, is_staff=True, email="bernardo@companyemail.com") self.client.force_login(self.user) self.psf = baker.make("sponsors.SponsorshipProgram", name="PSF") - self.program_1_benefits = baker.make( - SponsorshipBenefit, program=self.psf, _quantity=3, year=self.current_year - ) + self.program_1_benefits = baker.make(SponsorshipBenefit, program=self.psf, _quantity=3, year=self.current_year) self.package = baker.make(SponsorshipPackage, advertise=True, year=self.current_year) for benefit in self.program_1_benefits: benefit.packages.add(self.package) @@ -248,13 +231,9 @@ def test_display_template_with_form_and_context_without_a_la_carte(self): self.assertTemplateUsed(r, "sponsors/new_sponsorship_application_form.html") self.assertIsInstance(r.context["form"], SponsorshipApplicationForm) self.assertEqual(r.context["sponsorship_package"], self.package) - self.assertEqual( - len(r.context["sponsorship_benefits"]), len(self.program_1_benefits) - ) + self.assertEqual(len(r.context["sponsorship_benefits"]), len(self.program_1_benefits)) self.assertEqual(len(r.context["added_benefits"]), 0) - self.assertEqual( - r.context["sponsorship_price"], self.package.sponsorship_amount - ) + self.assertEqual(r.context["sponsorship_price"], self.package.sponsorship_amount) for benefit in self.program_1_benefits: self.assertIn(benefit, r.context["sponsorship_benefits"]) @@ -342,20 +321,14 @@ def test_create_new_sponsorship(self): self.assertEqual(r.context["notified"], ["bernardo@companyemail.com"]) self.assertTrue(Sponsor.objects.filter(name="CompanyX").exists()) - self.assertTrue( - SponsorContact.objects.filter( - sponsor__name="CompanyX", user=self.user - ).exists() - ) + self.assertTrue(SponsorContact.objects.filter(sponsor__name="CompanyX", user=self.user).exists()) sponsorship = Sponsorship.objects.get(sponsor__name="CompanyX") self.assertTrue(sponsorship.benefits.exists()) # 3 benefits + 1 a-la-carte + 0 standalone self.assertEqual(4, sponsorship.benefits.count()) self.assertTrue(sponsorship.level_name) self.assertTrue(sponsorship.submited_by, self.user) - self.assertEqual( - r.client.cookies.get("sponsorship_selected_benefits").value, "" - ) + self.assertEqual(r.client.cookies.get("sponsorship_selected_benefits").value, "") self.assertTrue(mail.outbox) def test_redirect_user_back_to_benefits_selection_if_post_without_valid_set_of_benefits( @@ -371,23 +344,17 @@ def test_redirect_user_back_to_benefits_selection_if_post_without_valid_set_of_b assertMessage(r_messages[0], redirect_msg, redirect_lvl) self.assertRedirects(r, reverse("select_sponsorship_application_benefits")) - self.data["web_logo"] = get_static_image_file_as_upload( - "psf-logo.png", "logo.png" - ) + self.data["web_logo"] = get_static_image_file_as_upload("psf-logo.png", "logo.png") self.client.cookies["sponsorship_selected_benefits"] = "" r = self.client.post(self.url, data=self.data) self.assertRedirects(r, reverse("select_sponsorship_application_benefits")) - self.data["web_logo"] = get_static_image_file_as_upload( - "psf-logo.png", "logo.png" - ) + self.data["web_logo"] = get_static_image_file_as_upload("psf-logo.png", "logo.png") self.client.cookies["sponsorship_selected_benefits"] = "{}" r = self.client.post(self.url, data=self.data) self.assertRedirects(r, reverse("select_sponsorship_application_benefits")) - self.data["web_logo"] = get_static_image_file_as_upload( - "psf-logo.png", "logo.png" - ) + self.data["web_logo"] = get_static_image_file_as_upload("psf-logo.png", "logo.png") self.client.cookies["sponsorship_selected_benefits"] = "invalid" r = self.client.post(self.url, data=self.data) self.assertRedirects(r, reverse("select_sponsorship_application_benefits")) diff --git a/sponsors/tests/test_views_admin.py b/sponsors/tests/test_views_admin.py index 1b260187a..c7df55063 100644 --- a/sponsors/tests/test_views_admin.py +++ b/sponsors/tests/test_views_admin.py @@ -19,19 +19,32 @@ from django.urls import reverse from .utils import assertMessage, get_static_image_file_as_upload -from ..models import Sponsorship, Contract, SponsorshipBenefit, SponsorBenefit, SponsorEmailNotificationTemplate, \ - GenericAsset, ImgAsset, TextAsset, SponsorshipCurrentYear, SponsorshipPackage -from ..forms import SponsorshipReviewAdminForm, SponsorshipsListForm, SignedSponsorshipReviewAdminForm, \ - SendSponsorshipNotificationForm, CloneApplicationConfigForm +from ..models import ( + Sponsorship, + Contract, + SponsorshipBenefit, + SponsorBenefit, + SponsorEmailNotificationTemplate, + GenericAsset, + ImgAsset, + TextAsset, + SponsorshipCurrentYear, + SponsorshipPackage, +) +from ..forms import ( + SponsorshipReviewAdminForm, + SponsorshipsListForm, + SignedSponsorshipReviewAdminForm, + SendSponsorshipNotificationForm, + CloneApplicationConfigForm, +) from sponsors.views_admin import send_sponsorship_notifications_action, export_assets_as_zipfile from sponsors.use_cases import SendSponsorshipNotificationUseCase class RollbackSponsorshipToEditingAdminViewTests(TestCase): def setUp(self): - self.user = baker.make( - settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True - ) + self.user = baker.make(settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True) self.client.force_login(self.user) self.sponsorship = baker.make( Sponsorship, @@ -39,31 +52,23 @@ def setUp(self): submited_by=self.user, _fill_optional=True, ) - self.url = reverse( - "admin:sponsors_sponsorship_rollback_to_edit", args=[self.sponsorship.pk] - ) + self.url = reverse("admin:sponsors_sponsorship_rollback_to_edit", args=[self.sponsorship.pk]) def test_display_confirmation_form_on_get(self): response = self.client.get(self.url) context = response.context self.sponsorship.refresh_from_db() - self.assertTemplateUsed( - response, "sponsors/admin/rollback_sponsorship_to_editing.html" - ) + self.assertTemplateUsed(response, "sponsors/admin/rollback_sponsorship_to_editing.html") self.assertEqual(context["sponsorship"], self.sponsorship) - self.assertNotEqual( - self.sponsorship.status, Sponsorship.APPLIED - ) # did not update + self.assertNotEqual(self.sponsorship.status, Sponsorship.APPLIED) # did not update def test_rollback_sponsorship_to_applied_on_post(self): data = {"confirm": "yes"} response = self.client.post(self.url, data=data) self.sponsorship.refresh_from_db() - expected_url = reverse( - "admin:sponsors_sponsorship_change", args=[self.sponsorship.pk] - ) + expected_url = reverse("admin:sponsors_sponsorship_change", args=[self.sponsorship.pk]) self.assertRedirects(response, expected_url, fetch_redirect_response=True) self.assertEqual(self.sponsorship.status, Sponsorship.APPLIED) msg = list(get_messages(response.wsgi_request))[0] @@ -72,18 +77,12 @@ def test_rollback_sponsorship_to_applied_on_post(self): def test_do_not_rollback_if_invalid_post(self): response = self.client.post(self.url, data={}) self.sponsorship.refresh_from_db() - self.assertTemplateUsed( - response, "sponsors/admin/rollback_sponsorship_to_editing.html" - ) - self.assertNotEqual( - self.sponsorship.status, Sponsorship.APPLIED - ) # did not update + self.assertTemplateUsed(response, "sponsors/admin/rollback_sponsorship_to_editing.html") + self.assertNotEqual(self.sponsorship.status, Sponsorship.APPLIED) # did not update response = self.client.post(self.url, data={"confirm": "invalid"}) self.sponsorship.refresh_from_db() - self.assertTemplateUsed( - response, "sponsors/admin/rollback_sponsorship_to_editing.html" - ) + self.assertTemplateUsed(response, "sponsors/admin/rollback_sponsorship_to_editing.html") self.assertNotEqual(self.sponsorship.status, Sponsorship.APPLIED) def test_404_if_sponsorship_does_not_exist(self): @@ -118,22 +117,16 @@ def test_message_user_if_rejecting_invalid_sponsorship(self): response = self.client.post(self.url, data=data) self.sponsorship.refresh_from_db() - expected_url = reverse( - "admin:sponsors_sponsorship_change", args=[self.sponsorship.pk] - ) + expected_url = reverse("admin:sponsors_sponsorship_change", args=[self.sponsorship.pk]) self.assertRedirects(response, expected_url, fetch_redirect_response=True) self.assertEqual(self.sponsorship.status, Sponsorship.FINALIZED) msg = list(get_messages(response.wsgi_request))[0] - assertMessage( - msg, "Can't rollback to edit a Finalized sponsorship.", messages.ERROR - ) + assertMessage(msg, "Can't rollback to edit a Finalized sponsorship.", messages.ERROR) class RejectedSponsorshipAdminViewTests(TestCase): def setUp(self): - self.user = baker.make( - settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True - ) + self.user = baker.make(settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True) self.client.force_login(self.user) self.sponsorship = baker.make( Sponsorship, @@ -141,9 +134,7 @@ def setUp(self): submited_by=self.user, _fill_optional=True, ) - self.url = reverse( - "admin:sponsors_sponsorship_reject", args=[self.sponsorship.pk] - ) + self.url = reverse("admin:sponsors_sponsorship_reject", args=[self.sponsorship.pk]) def test_display_confirmation_form_on_get(self): response = self.client.get(self.url) @@ -152,18 +143,14 @@ def test_display_confirmation_form_on_get(self): self.assertTemplateUsed(response, "sponsors/admin/reject_application.html") self.assertEqual(context["sponsorship"], self.sponsorship) - self.assertNotEqual( - self.sponsorship.status, Sponsorship.REJECTED - ) # did not update + self.assertNotEqual(self.sponsorship.status, Sponsorship.REJECTED) # did not update def test_reject_sponsorship_on_post(self): data = {"confirm": "yes"} response = self.client.post(self.url, data=data) self.sponsorship.refresh_from_db() - expected_url = reverse( - "admin:sponsors_sponsorship_change", args=[self.sponsorship.pk] - ) + expected_url = reverse("admin:sponsors_sponsorship_change", args=[self.sponsorship.pk]) self.assertRedirects(response, expected_url, fetch_redirect_response=True) self.assertTrue(mail.outbox) self.assertEqual(self.sponsorship.status, Sponsorship.REJECTED) @@ -174,9 +161,7 @@ def test_do_not_reject_if_invalid_post(self): response = self.client.post(self.url, data={}) self.sponsorship.refresh_from_db() self.assertTemplateUsed(response, "sponsors/admin/reject_application.html") - self.assertNotEqual( - self.sponsorship.status, Sponsorship.REJECTED - ) # did not update + self.assertNotEqual(self.sponsorship.status, Sponsorship.REJECTED) # did not update response = self.client.post(self.url, data={"confirm": "invalid"}) self.sponsorship.refresh_from_db() @@ -215,9 +200,7 @@ def test_message_user_if_rejecting_invalid_sponsorship(self): response = self.client.post(self.url, data=data) self.sponsorship.refresh_from_db() - expected_url = reverse( - "admin:sponsors_sponsorship_change", args=[self.sponsorship.pk] - ) + expected_url = reverse("admin:sponsors_sponsorship_change", args=[self.sponsorship.pk]) self.assertRedirects(response, expected_url, fetch_redirect_response=True) self.assertEqual(self.sponsorship.status, Sponsorship.FINALIZED) msg = list(get_messages(response.wsgi_request))[0] @@ -226,16 +209,10 @@ def test_message_user_if_rejecting_invalid_sponsorship(self): class ApproveSponsorshipAdminViewTests(TestCase): def setUp(self): - self.user = baker.make( - settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True - ) + self.user = baker.make(settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True) self.client.force_login(self.user) - self.sponsorship = baker.make( - Sponsorship, status=Sponsorship.APPLIED, _fill_optional=True - ) - self.url = reverse( - "admin:sponsors_sponsorship_approve", args=[self.sponsorship.pk] - ) + self.sponsorship = baker.make(Sponsorship, status=Sponsorship.APPLIED, _fill_optional=True) + self.url = reverse("admin:sponsors_sponsorship_approve", args=[self.sponsorship.pk]) today = date.today() self.package = baker.make("sponsors.SponsorshipPackage") self.data = { @@ -258,21 +235,15 @@ def test_display_confirmation_form_on_get(self): self.assertEqual(form.initial["package"], self.sponsorship.package) self.assertEqual(form.initial["start_date"], self.sponsorship.start_date) self.assertEqual(form.initial["end_date"], self.sponsorship.end_date) - self.assertEqual( - form.initial["sponsorship_fee"], self.sponsorship.sponsorship_fee - ) - self.assertNotEqual( - self.sponsorship.status, Sponsorship.APPROVED - ) # did not update + self.assertEqual(form.initial["sponsorship_fee"], self.sponsorship.sponsorship_fee) + self.assertNotEqual(self.sponsorship.status, Sponsorship.APPROVED) # did not update def test_approve_sponsorship_on_post(self): response = self.client.post(self.url, data=self.data) self.sponsorship.refresh_from_db() - expected_url = reverse( - "admin:sponsors_sponsorship_change", args=[self.sponsorship.pk] - ) + expected_url = reverse("admin:sponsors_sponsorship_change", args=[self.sponsorship.pk]) self.assertRedirects(response, expected_url, fetch_redirect_response=True) self.assertEqual(self.sponsorship.status, Sponsorship.APPROVED) msg = list(get_messages(response.wsgi_request))[0] @@ -283,9 +254,7 @@ def test_do_not_approve_if_no_confirmation_in_the_post(self): response = self.client.post(self.url, data=self.data) self.sponsorship.refresh_from_db() self.assertTemplateUsed(response, "sponsors/admin/approve_application.html") - self.assertNotEqual( - self.sponsorship.status, Sponsorship.APPROVED - ) # did not update + self.assertNotEqual(self.sponsorship.status, Sponsorship.APPROVED) # did not update self.data["confirm"] = "invalid" response = self.client.post(self.url, data=self.data) @@ -298,9 +267,7 @@ def test_do_not_approve_if_form_with_invalid_data(self): response = self.client.post(self.url, data=self.data) self.sponsorship.refresh_from_db() self.assertTemplateUsed(response, "sponsors/admin/approve_application.html") - self.assertNotEqual( - self.sponsorship.status, Sponsorship.APPROVED - ) # did not update + self.assertNotEqual(self.sponsorship.status, Sponsorship.APPROVED) # did not update self.assertTrue(response.context["form"].errors) def test_404_if_sponsorship_does_not_exist(self): @@ -334,9 +301,7 @@ def test_message_user_if_approving_invalid_sponsorship(self): response = self.client.post(self.url, data=self.data) self.sponsorship.refresh_from_db() - expected_url = reverse( - "admin:sponsors_sponsorship_change", args=[self.sponsorship.pk] - ) + expected_url = reverse("admin:sponsors_sponsorship_change", args=[self.sponsorship.pk]) self.assertRedirects(response, expected_url, fetch_redirect_response=True) self.assertEqual(self.sponsorship.status, Sponsorship.FINALIZED) msg = list(get_messages(response.wsgi_request))[0] @@ -345,16 +310,10 @@ def test_message_user_if_approving_invalid_sponsorship(self): class ApproveSignedSponsorshipAdminViewTests(TestCase): def setUp(self): - self.user = baker.make( - settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True - ) + self.user = baker.make(settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True) self.client.force_login(self.user) - self.sponsorship = baker.make( - Sponsorship, status=Sponsorship.APPLIED, _fill_optional=True - ) - self.url = reverse( - "admin:sponsors_sponsorship_approve_existing_contract", args=[self.sponsorship.pk] - ) + self.sponsorship = baker.make(Sponsorship, status=Sponsorship.APPLIED, _fill_optional=True) + self.url = reverse("admin:sponsors_sponsorship_approve_existing_contract", args=[self.sponsorship.pk]) today = date.today() self.package = baker.make("sponsors.SponsorshipPackage") self.data = { @@ -363,7 +322,7 @@ def setUp(self): "end_date": today + timedelta(days=100), "package": self.package.pk, "sponsorship_fee": 500, - "signed_contract": io.BytesIO(b"Signed contract") + "signed_contract": io.BytesIO(b"Signed contract"), } def test_display_confirmation_form_on_get(self): @@ -378,12 +337,8 @@ def test_display_confirmation_form_on_get(self): self.assertEqual(form.initial["package"], self.sponsorship.package) self.assertEqual(form.initial["start_date"], self.sponsorship.start_date) self.assertEqual(form.initial["end_date"], self.sponsorship.end_date) - self.assertEqual( - form.initial["sponsorship_fee"], self.sponsorship.sponsorship_fee - ) - self.assertNotEqual( - self.sponsorship.status, Sponsorship.APPROVED - ) # did not update + self.assertEqual(form.initial["sponsorship_fee"], self.sponsorship.sponsorship_fee) + self.assertNotEqual(self.sponsorship.status, Sponsorship.APPROVED) # did not update def test_approve_sponsorship_and_execute_contract_on_post(self): response = self.client.post(self.url, data=self.data) @@ -391,9 +346,7 @@ def test_approve_sponsorship_and_execute_contract_on_post(self): self.sponsorship.refresh_from_db() contract = self.sponsorship.contract - expected_url = reverse( - "admin:sponsors_sponsorship_change", args=[self.sponsorship.pk] - ) + expected_url = reverse("admin:sponsors_sponsorship_change", args=[self.sponsorship.pk]) self.assertRedirects(response, expected_url, fetch_redirect_response=True) self.assertEqual(self.sponsorship.status, Sponsorship.FINALIZED) self.assertEqual(contract.status, Contract.EXECUTED) @@ -406,9 +359,7 @@ def test_do_not_approve_if_no_confirmation_in_the_post(self): response = self.client.post(self.url, data=self.data) self.sponsorship.refresh_from_db() self.assertTemplateUsed(response, "sponsors/admin/approve_application.html") - self.assertNotEqual( - self.sponsorship.status, Sponsorship.APPROVED - ) # did not update + self.assertNotEqual(self.sponsorship.status, Sponsorship.APPROVED) # did not update self.data["confirm"] = "invalid" response = self.client.post(self.url, data=self.data) @@ -421,9 +372,7 @@ def test_do_not_approve_if_form_with_invalid_data(self): response = self.client.post(self.url, data=self.data) self.sponsorship.refresh_from_db() self.assertTemplateUsed(response, "sponsors/admin/approve_application.html") - self.assertNotEqual( - self.sponsorship.status, Sponsorship.APPROVED - ) # did not update + self.assertNotEqual(self.sponsorship.status, Sponsorship.APPROVED) # did not update self.assertTrue(response.context["form"].errors) def test_404_if_sponsorship_does_not_exist(self): @@ -457,9 +406,7 @@ def test_message_user_if_approving_invalid_sponsorship(self): response = self.client.post(self.url, data=self.data) self.sponsorship.refresh_from_db() - expected_url = reverse( - "admin:sponsors_sponsorship_change", args=[self.sponsorship.pk] - ) + expected_url = reverse("admin:sponsors_sponsorship_change", args=[self.sponsorship.pk]) self.assertRedirects(response, expected_url, fetch_redirect_response=True) self.assertEqual(self.sponsorship.status, Sponsorship.FINALIZED) msg = list(get_messages(response.wsgi_request))[0] @@ -468,14 +415,10 @@ def test_message_user_if_approving_invalid_sponsorship(self): class SendContractViewTests(TestCase): def setUp(self): - self.user = baker.make( - settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True - ) + self.user = baker.make(settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True) self.client.force_login(self.user) self.contract = baker.make_recipe("sponsors.tests.empty_contract") - self.url = reverse( - "admin:sponsors_contract_send", args=[self.contract.pk] - ) + self.url = reverse("admin:sponsors_contract_send", args=[self.contract.pk]) self.data = { "confirm": "yes", } @@ -487,14 +430,10 @@ def test_display_confirmation_form_on_get(self): self.assertTemplateUsed(response, "sponsors/admin/send_contract.html") self.assertEqual(context["contract"], self.contract) - @patch.object( - Sponsorship, "verified_emails", PropertyMock(return_value=["email@email.com"]) - ) + @patch.object(Sponsorship, "verified_emails", PropertyMock(return_value=["email@email.com"])) def test_approve_sponsorship_on_post(self): response = self.client.post(self.url, data=self.data) - expected_url = reverse( - "admin:sponsors_contract_change", args=[self.contract.pk] - ) + expected_url = reverse("admin:sponsors_contract_change", args=[self.contract.pk]) self.contract.refresh_from_db() self.assertRedirects(response, expected_url, fetch_redirect_response=True) @@ -503,15 +442,11 @@ def test_approve_sponsorship_on_post(self): msg = list(get_messages(response.wsgi_request))[0] assertMessage(msg, "Contract was sent!", messages.SUCCESS) - @patch.object( - Sponsorship, "verified_emails", PropertyMock(return_value=["email@email.com"]) - ) + @patch.object(Sponsorship, "verified_emails", PropertyMock(return_value=["email@email.com"])) def test_display_error_message_to_user_if_invalid_status(self): self.contract.status = Contract.AWAITING_SIGNATURE self.contract.save() - expected_url = reverse( - "admin:sponsors_contract_change", args=[self.contract.pk] - ) + expected_url = reverse("admin:sponsors_contract_change", args=[self.contract.pk]) response = self.client.post(self.url, data=self.data) self.contract.refresh_from_db() @@ -566,14 +501,10 @@ def test_staff_required(self): class ExecuteContractViewTests(TestCase): def setUp(self): - self.user = baker.make( - settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True - ) + self.user = baker.make(settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True) self.client.force_login(self.user) self.contract = baker.make_recipe("sponsors.tests.empty_contract", status=Contract.AWAITING_SIGNATURE) - self.url = reverse( - "admin:sponsors_contract_execute", args=[self.contract.pk] - ) + self.url = reverse("admin:sponsors_contract_execute", args=[self.contract.pk]) self.data = { "confirm": "yes", "signed_document": SimpleUploadedFile("contract.txt", b"Contract content"), @@ -596,9 +527,7 @@ def test_display_confirmation_form_on_get(self): def test_execute_sponsorship_on_post(self): response = self.client.post(self.url, data=self.data) - expected_url = reverse( - "admin:sponsors_contract_change", args=[self.contract.pk] - ) + expected_url = reverse("admin:sponsors_contract_change", args=[self.contract.pk]) self.contract.refresh_from_db() msg = list(get_messages(response.wsgi_request))[0] @@ -609,9 +538,7 @@ def test_execute_sponsorship_on_post(self): def test_display_error_message_to_user_if_invalid_status(self): self.contract.status = Contract.OUTDATED self.contract.save() - expected_url = reverse( - "admin:sponsors_contract_change", args=[self.contract.pk] - ) + expected_url = reverse("admin:sponsors_contract_change", args=[self.contract.pk]) response = self.client.post(self.url, data=self.data) self.contract.refresh_from_db() @@ -675,14 +602,10 @@ def test_staff_required(self): class NullifyContractViewTests(TestCase): def setUp(self): - self.user = baker.make( - settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True - ) + self.user = baker.make(settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True) self.client.force_login(self.user) self.contract = baker.make_recipe("sponsors.tests.empty_contract", status=Contract.AWAITING_SIGNATURE) - self.url = reverse( - "admin:sponsors_contract_nullify", args=[self.contract.pk] - ) + self.url = reverse("admin:sponsors_contract_nullify", args=[self.contract.pk]) self.data = { "confirm": "yes", } @@ -696,9 +619,7 @@ def test_display_confirmation_form_on_get(self): def test_nullify_sponsorship_on_post(self): response = self.client.post(self.url, data=self.data) - expected_url = reverse( - "admin:sponsors_contract_change", args=[self.contract.pk] - ) + expected_url = reverse("admin:sponsors_contract_change", args=[self.contract.pk]) self.contract.refresh_from_db() msg = list(get_messages(response.wsgi_request))[0] @@ -709,9 +630,7 @@ def test_nullify_sponsorship_on_post(self): def test_display_error_message_to_user_if_invalid_status(self): self.contract.status = Contract.DRAFT self.contract.save() - expected_url = reverse( - "admin:sponsors_contract_change", args=[self.contract.pk] - ) + expected_url = reverse("admin:sponsors_contract_change", args=[self.contract.pk]) response = self.client.post(self.url, data=self.data) self.contract.refresh_from_db() @@ -766,9 +685,7 @@ def test_staff_required(self): class UpdateRelatedSponsorshipsTests(TestCase): def setUp(self): - self.user = baker.make( - settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True - ) + self.user = baker.make(settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True) self.client.force_login(self.user) self.benefit = baker.make(SponsorshipBenefit) self.sponsor_benefit = baker.make( @@ -777,9 +694,7 @@ def setUp(self): sponsorship__sponsor__name="Foo", added_by_user=True, # to make sure we keep previous fields ) - self.url = reverse( - "admin:sponsors_sponsorshipbenefit_update_related", args=[self.benefit.pk] - ) + self.url = reverse("admin:sponsors_sponsorshipbenefit_update_related", args=[self.benefit.pk]) self.data = {"sponsorships": [self.sponsor_benefit.sponsorship.pk]} def test_display_form_from_benefit_on_get(self): @@ -814,9 +729,7 @@ def test_bad_request_if_invalid_post_data(self): self.assertTrue(response.context["form"].errors) def test_redirect_back_to_benefit_page_if_success(self): - redirect_url = reverse( - "admin:sponsors_sponsorshipbenefit_change", args=[self.benefit.pk] - ) + redirect_url = reverse("admin:sponsors_sponsorshipbenefit_change", args=[self.benefit.pk]) response = self.client.post(self.url, data=self.data) self.assertRedirects(response, redirect_url) @@ -832,8 +745,8 @@ def test_update_selected_sponsorships_only(self): description=self.benefit.description, ) prev_name, prev_description = self.benefit.name, self.benefit.description - self.benefit.name = 'New name' - self.benefit.description = 'New description' + self.benefit.name = "New name" + self.benefit.description = "New description" self.benefit.save() response = self.client.post(self.url, data=self.data) @@ -875,16 +788,10 @@ def test_staff_required(self): class PreviewContractViewTests(TestCase): def setUp(self): - self.user = baker.make( - settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True - ) + self.user = baker.make(settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True) self.client.force_login(self.user) - self.contract = baker.make_recipe( - "sponsors.tests.empty_contract", sponsorship__start_date=date.today() - ) - self.url = reverse( - "admin:sponsors_contract_preview", args=[self.contract.pk] - ) + self.contract = baker.make_recipe("sponsors.tests.empty_contract", sponsorship__start_date=date.today()) + self.url = reverse("admin:sponsors_contract_preview", args=[self.contract.pk]) @patch("sponsors.views_admin.render_contract_to_pdf_response") def test_render_pdf_by_default(self, mocked_render): @@ -915,9 +822,7 @@ def test_render_docx_if_specified_in_the_querystring(self, mocked_render): class PreviewSponsorEmailNotificationTemplateTests(TestCase): def setUp(self): - self.user = baker.make( - settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True - ) + self.user = baker.make(settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True) self.client.force_login(self.user) self.sponsor_notification = baker.make(SponsorEmailNotificationTemplate, content="{{'content'|upper}}") self.url = self.sponsor_notification.preview_content_url @@ -954,11 +859,8 @@ def test_staff_required(self): class ClonsSponsorshipYearConfigurationTests(TestCase): - def setUp(self): - self.user = baker.make( - settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True - ) + self.user = baker.make(settings.AUTH_USER_MODEL, is_staff=True, is_superuser=True) self.client.force_login(self.user) self.url = reverse("admin:sponsors_sponsorshipcurrentyear_clone") @@ -1013,12 +915,11 @@ def test_clone_sponsorship_application_config_with_valid_post(self): ####################### ### TEST CUSTOM ACTIONS class SendSponsorshipNotificationTests(TestCase): - def setUp(self): self.request_factory = RequestFactory() - baker.make(Sponsorship, _quantity=3, sponsor__name='foo') + baker.make(Sponsorship, _quantity=3, sponsor__name="foo") self.sponsorship = Sponsorship.objects.all()[0] - baker.make('sponsors.EmailTargetable', sponsor_benefit__sponsorship=self.sponsorship) + baker.make("sponsors.EmailTargetable", sponsor_benefit__sponsorship=self.sponsorship) self.queryset = Sponsorship.objects.all() self.user = baker.make("users.User") @@ -1077,12 +978,11 @@ def test_call_use_case_and_redirect_with_success(self, mock_build): class ExportAssetsAsZipTests(TestCase): - def setUp(self): self.request_factory = RequestFactory() self.request = self.request_factory.get("/") self.request.user = baker.make("users.User") - self.sponsorship = baker.make(Sponsorship, sponsor__name='Sponsor Name') + self.sponsorship = baker.make(Sponsorship, sponsor__name="Sponsor Name") self.ModelAdmin = Mock() self.text_asset = TextAsset.objects.create( uuid=uuid4(), @@ -1118,7 +1018,7 @@ def test_display_same_page_with_warning_message_if_any_asset_without_value(self) def test_response_is_configured_to_be_zip_file(self): self.text_asset.value = "foo" - self.img_asset.value = SimpleUploadedFile(name='test_image.jpg', content=b"content", content_type='image/jpeg') + self.img_asset.value = SimpleUploadedFile(name="test_image.jpg", content=b"content", content_type="image/jpeg") self.text_asset.save() self.img_asset.save() diff --git a/sponsors/tests/utils.py b/sponsors/tests/utils.py index 66edd982f..9aac486a8 100644 --- a/sponsors/tests/utils.py +++ b/sponsors/tests/utils.py @@ -15,6 +15,4 @@ def get_static_image_file_as_upload(filename, upload_filename=None): def assertMessage(msg, expected_content, expected_level): assert msg.level == expected_level, f"Message {msg} level is not {expected_level}" - assert ( - str(msg) == expected_content - ), f"Message {msg} content is not {expected_content}" + assert str(msg) == expected_content, f"Message {msg} content is not {expected_content}" diff --git a/sponsors/urls.py b/sponsors/urls.py index d658dffe6..a107d8d40 100644 --- a/sponsors/urls.py +++ b/sponsors/urls.py @@ -4,10 +4,14 @@ urlpatterns = [ - path('application/new/', views.NewSponsorshipApplicationView.as_view(), + path( + "application/new/", + views.NewSponsorshipApplicationView.as_view(), name="new_sponsorship_application", ), - path('application/', views.SelectSponsorshipApplicationBenefitsView.as_view(), + path( + "application/", + views.SelectSponsorshipApplicationBenefitsView.as_view(), name="select_sponsorship_application_benefits", ), ] diff --git a/sponsors/use_cases.py b/sponsors/use_cases.py index 91271ff64..c6f4f0032 100644 --- a/sponsors/use_cases.py +++ b/sponsors/use_cases.py @@ -1,8 +1,14 @@ from django.db import transaction from sponsors import notifications -from sponsors.models import Sponsorship, Contract, SponsorContact, SponsorEmailNotificationTemplate, SponsorshipBenefit, \ - SponsorshipPackage +from sponsors.models import ( + Sponsorship, + Contract, + SponsorContact, + SponsorEmailNotificationTemplate, + SponsorshipBenefit, + SponsorshipPackage, +) from sponsors.contracts import render_contract_to_pdf_file, render_contract_to_docx_file @@ -83,7 +89,7 @@ class SendContractUseCase(BaseUseCaseWithNotifications): # the generate contract file gets approved by PSF Board. # After that, the line bellow can be uncommented to enable # the desired behavior. - #notifications.ContractNotificationToSponsors(), + # notifications.ContractNotificationToSponsors(), notifications.SentContractLogger(), ] @@ -107,11 +113,14 @@ class ExecuteExistingContractUseCase(BaseUseCaseWithNotifications): def execute(self, contract, contract_file, **kwargs): contract.signed_document = contract_file contract.execute(force=self.force_execute) - overlapping_sponsorship = Sponsorship.objects.filter( - sponsor=contract.sponsorship.sponsor, - ).exclude( - id=contract.sponsorship.id - ).enabled().active_on_date(contract.sponsorship.start_date) + overlapping_sponsorship = ( + Sponsorship.objects.filter( + sponsor=contract.sponsorship.sponsor, + ) + .exclude(id=contract.sponsorship.id) + .enabled() + .active_on_date(contract.sponsorship.start_date) + ) overlapping_sponsorship.update(overlapped_by=contract.sponsorship) self.notify( request=kwargs.get("request"), diff --git a/sponsors/views.py b/sponsors/views.py index dccd8446d..f8a2f209d 100644 --- a/sponsors/views.py +++ b/sponsors/views.py @@ -12,7 +12,8 @@ from .models import ( SponsorshipBenefit, SponsorshipPackage, - SponsorshipProgram, SponsorshipCurrentYear, + SponsorshipProgram, + SponsorshipCurrentYear, ) from sponsors import cookies @@ -28,12 +29,7 @@ def get_context_data(self, *args, **kwargs): programs = SponsorshipProgram.objects.all() packages = SponsorshipPackage.objects.all() benefits_qs = SponsorshipBenefit.objects.select_related("program") - capacities_met = any( - [ - any([not b.has_capacity for b in benefits_qs.filter(program=p)]) - for p in programs - ] - ) + capacities_met = any([any([not b.has_capacity for b in benefits_qs.filter(program=p)]) for p in programs]) kwargs.update( { "benefit_model": SponsorshipBenefit, @@ -90,7 +86,7 @@ def _set_form_data_cookie(self, form, response): for fname, benefits in [ (f, v) for f, v in form.cleaned_data.items() - if f.startswith("benefits_") or f in ['a_la_carte_benefits', 'standalone_benefits'] + if f.startswith("benefits_") or f in ["a_la_carte_benefits", "standalone_benefits"] ]: data[fname] = sorted(b.id for b in benefits) @@ -123,12 +119,8 @@ def get_form_kwargs(self, *args, **kwargs): def get_context_data(self, *args, **kwargs): package_id = self.benefits_data.get("package") - package = ( - None if not package_id else SponsorshipPackage.objects.get(id=package_id) - ) - benefits_ids = chain( - *(self.benefits_data[k] for k in self.benefits_data if k != "package") - ) + package = None if not package_id else SponsorshipPackage.objects.get(id=package_id) + benefits_ids = chain(*(self.benefits_data[k] for k in self.benefits_data if k != "package")) benefits = SponsorshipBenefit.objects.filter(id__in=benefits_ids) # sponsorship benefits holds selected package's benefits @@ -174,9 +166,7 @@ def form_valid(self, form): benefits_form.get_package(), request=self.request, ) - notified = uc.notifications[1].get_recipient_list( - {"user": self.request.user, "sponsorship": sponsorship} - ) + notified = uc.notifications[1].get_recipient_list({"user": self.request.user, "sponsorship": sponsorship}) response = render( self.request, diff --git a/sponsors/views_admin.py b/sponsors/views_admin.py index fd8631d3f..87e142310 100644 --- a/sponsors/views_admin.py +++ b/sponsors/views_admin.py @@ -11,18 +11,31 @@ from django.db import transaction from sponsors import use_cases -from sponsors.forms import SponsorshipReviewAdminForm, SponsorshipsListForm, SignedSponsorshipReviewAdminForm, \ - SendSponsorshipNotificationForm, CloneApplicationConfigForm +from sponsors.forms import ( + SponsorshipReviewAdminForm, + SponsorshipsListForm, + SignedSponsorshipReviewAdminForm, + SendSponsorshipNotificationForm, + CloneApplicationConfigForm, +) from sponsors.exceptions import InvalidStatusException from sponsors.contracts import render_contract_to_pdf_response, render_contract_to_docx_response -from sponsors.models import Sponsorship, SponsorBenefit, EmailTargetable, SponsorContact, BenefitFeature, \ - SponsorshipCurrentYear, SponsorshipBenefit, SponsorshipPackage +from sponsors.models import ( + Sponsorship, + SponsorBenefit, + EmailTargetable, + SponsorContact, + BenefitFeature, + SponsorshipCurrentYear, + SponsorshipBenefit, + SponsorshipPackage, +) def preview_contract_view(ModelAdmin, request, pk): contract = get_object_or_404(ModelAdmin.get_queryset(request), pk=pk) - format = request.GET.get('format', 'pdf') - if format == 'docx': + format = request.GET.get("format", "pdf") + if format == "docx": response = render_contract_to_docx_response(request, contract) else: response = render_contract_to_pdf_response(request, contract) @@ -37,15 +50,11 @@ def reject_sponsorship_view(ModelAdmin, request, pk): try: use_case = use_cases.RejectSponsorshipApplicationUseCase.build() use_case.execute(sponsorship) - ModelAdmin.message_user( - request, "Sponsorship was rejected!", messages.SUCCESS - ) + ModelAdmin.message_user(request, "Sponsorship was rejected!", messages.SUCCESS) except InvalidStatusException as e: ModelAdmin.message_user(request, str(e), messages.ERROR) - redirect_url = reverse( - "admin:sponsors_sponsorship_change", args=[sponsorship.pk] - ) + redirect_url = reverse("admin:sponsors_sponsorship_change", args=[sponsorship.pk]) return redirect(redirect_url) context = {"sponsorship": sponsorship} @@ -74,15 +83,11 @@ def approve_sponsorship_view(ModelAdmin, request, pk): try: use_case = use_cases.ApproveSponsorshipApplicationUseCase.build() use_case.execute(sponsorship, **kwargs) - ModelAdmin.message_user( - request, "Sponsorship was approved!", messages.SUCCESS - ) + ModelAdmin.message_user(request, "Sponsorship was approved!", messages.SUCCESS) except InvalidStatusException as e: ModelAdmin.message_user(request, str(e), messages.ERROR) - redirect_url = reverse( - "admin:sponsors_sponsorship_change", args=[sponsorship.pk] - ) + redirect_url = reverse("admin:sponsors_sponsorship_change", args=[sponsorship.pk]) return redirect(redirect_url) context = { @@ -119,15 +124,11 @@ def approve_signed_sponsorship_view(ModelAdmin, request, pk): # execute it using existing contract use_case = use_cases.ExecuteExistingContractUseCase.build() use_case.execute(sponsorship.contract, kwargs["signed_contract"], request=request) - ModelAdmin.message_user( - request, "Signed sponsorship was approved!", messages.SUCCESS - ) + ModelAdmin.message_user(request, "Signed sponsorship was approved!", messages.SUCCESS) except InvalidStatusException as e: ModelAdmin.message_user(request, str(e), messages.ERROR) - redirect_url = reverse( - "admin:sponsors_sponsorship_change", args=[sponsorship.pk] - ) + redirect_url = reverse("admin:sponsors_sponsorship_change", args=[sponsorship.pk]) return redirect(redirect_url) context = {"sponsorship": sponsorship, "form": form} @@ -138,13 +139,10 @@ def send_contract_view(ModelAdmin, request, pk): contract = get_object_or_404(ModelAdmin.get_queryset(request), pk=pk) if request.method.upper() == "POST" and request.POST.get("confirm") == "yes": - use_case = use_cases.SendContractUseCase.build() try: use_case.execute(contract, request=request) - ModelAdmin.message_user( - request, "Contract was sent!", messages.SUCCESS - ) + ModelAdmin.message_user(request, "Contract was sent!", messages.SUCCESS) except InvalidStatusException: status = contract.get_status_display().title() ModelAdmin.message_user( @@ -167,15 +165,11 @@ def rollback_to_editing_view(ModelAdmin, request, pk): try: sponsorship.rollback_to_editing() sponsorship.save() - ModelAdmin.message_user( - request, "Sponsorship is now editable!", messages.SUCCESS - ) + ModelAdmin.message_user(request, "Sponsorship is now editable!", messages.SUCCESS) except InvalidStatusException as e: ModelAdmin.message_user(request, str(e), messages.ERROR) - redirect_url = reverse( - "admin:sponsors_sponsorship_change", args=[sponsorship.pk] - ) + redirect_url = reverse("admin:sponsors_sponsorship_change", args=[sponsorship.pk]) return redirect(redirect_url) context = {"sponsorship": sponsorship} @@ -192,16 +186,12 @@ def unlock_view(ModelAdmin, request, pk): if request.method.upper() == "POST" and request.POST.get("confirm") == "yes": try: sponsorship.locked = False - sponsorship.save(update_fields=['locked']) - ModelAdmin.message_user( - request, "Sponsorship is now unlocked!", messages.SUCCESS - ) + sponsorship.save(update_fields=["locked"]) + ModelAdmin.message_user(request, "Sponsorship is now unlocked!", messages.SUCCESS) except InvalidStatusException as e: ModelAdmin.message_user(request, str(e), messages.ERROR) - redirect_url = reverse( - "admin:sponsors_sponsorship_change", args=[sponsorship.pk] - ) + redirect_url = reverse("admin:sponsors_sponsorship_change", args=[sponsorship.pk]) return redirect(redirect_url) context = {"sponsorship": sponsorship} @@ -218,9 +208,7 @@ def lock_view(ModelAdmin, request, pk): sponsorship.locked = True sponsorship.save() - redirect_url = reverse( - "admin:sponsors_sponsorship_change", args=[sponsorship.pk] - ) + redirect_url = reverse("admin:sponsors_sponsorship_change", args=[sponsorship.pk]) return redirect(redirect_url) @@ -230,13 +218,10 @@ def execute_contract_view(ModelAdmin, request, pk): is_post = request.method.upper() == "POST" signed_document = request.FILES.get("signed_document") if is_post and request.POST.get("confirm") == "yes" and signed_document: - use_case = use_cases.ExecuteContractUseCase.build() try: use_case.execute(contract, signed_document, request=request) - ModelAdmin.message_user( - request, "Contract was executed!", messages.SUCCESS - ) + ModelAdmin.message_user(request, "Contract was executed!", messages.SUCCESS) except InvalidStatusException: status = contract.get_status_display().title() ModelAdmin.message_user( @@ -260,13 +245,10 @@ def nullify_contract_view(ModelAdmin, request, pk): contract = get_object_or_404(ModelAdmin.get_queryset(request), pk=pk) if request.method.upper() == "POST" and request.POST.get("confirm") == "yes": - use_case = use_cases.NullifyContractUseCase.build() try: use_case.execute(contract, request=request) - ModelAdmin.message_user( - request, "Contract was nullified!", messages.SUCCESS - ) + ModelAdmin.message_user(request, "Contract was nullified!", messages.SUCCESS) except InvalidStatusException: status = contract.get_status_display().title() ModelAdmin.message_user( @@ -303,12 +285,8 @@ def update_related_sponsorships(ModelAdmin, request, pk): sponsor_benefit = related_benefits.get(sponsorship=sp) sponsor_benefit.reset_attributes(benefit) - ModelAdmin.message_user( - request, f"{len(sponsorships)} related sponsorships updated!", messages.SUCCESS - ) - redirect_url = reverse( - "admin:sponsors_sponsorshipbenefit_change", args=[benefit.pk] - ) + ModelAdmin.message_user(request, f"{len(sponsorships)} related sponsorships updated!", messages.SUCCESS) + redirect_url = reverse("admin:sponsors_sponsorshipbenefit_change", args=[benefit.pk]) return redirect(redirect_url) context = {"benefit": benefit, "form": form} @@ -330,7 +308,7 @@ def clone_application_config(ModelAdmin, request): context = { "current_year": SponsorshipCurrentYear.get_year(), "configured_years": form.configured_years, - "new_year": None + "new_year": None, } if request.method == "POST": form = CloneApplicationConfigForm(data=request.POST) @@ -345,7 +323,7 @@ def clone_application_config(ModelAdmin, request): ModelAdmin.message_user( request, f"Benefits and Packages for {target_year} copied with sucess from {from_year}!", - messages.SUCCESS + messages.SUCCESS, ) context["form"] = form @@ -372,9 +350,7 @@ def send_sponsorship_notifications_action(ModelAdmin, request, queryset): "request": request, } use_case.execute(**kwargs) - ModelAdmin.message_user( - request, "Notifications were sent!", messages.SUCCESS - ) + ModelAdmin.message_user(request, "Notifications were sent!", messages.SUCCESS) redirect_url = reverse("admin:sponsors_sponsorship_changelist") return redirect(redirect_url) @@ -408,11 +384,7 @@ def export_assets_as_zipfile(ModelAdmin, request, queryset): directories to group assets from a same sponsor. """ if not queryset.exists(): - ModelAdmin.message_user( - request, - f"You have to select at least one asset to export.", - messages.WARNING - ) + ModelAdmin.message_user(request, f"You have to select at least one asset to export.", messages.WARNING) return redirect(request.path) assets_without_values = [asset for asset in queryset if not asset.has_value] @@ -420,12 +392,12 @@ def export_assets_as_zipfile(ModelAdmin, request, queryset): ModelAdmin.message_user( request, f"{len(assets_without_values)} assets from the selection doesn't have data to export. Please review your selection!", - messages.WARNING + messages.WARNING, ) return redirect(request.path) buffer = io.BytesIO() - zip_file = zipfile.ZipFile(buffer, 'w') + zip_file = zipfile.ZipFile(buffer, "w") for asset in queryset: zipdir = "unknown" # safety belt diff --git a/successstories/admin.py b/successstories/admin.py index bc15d2d11..6ee495e51 100644 --- a/successstories/admin.py +++ b/successstories/admin.py @@ -7,25 +7,23 @@ @admin.register(StoryCategory) class StoryCategoryAdmin(NameSlugAdmin): - prepopulated_fields = {'slug': ('name',)} + prepopulated_fields = {"slug": ("name",)} @admin.register(Story) class StoryAdmin(ContentManageableModelAdmin): - prepopulated_fields = {'slug': ('name',)} - raw_id_fields = ['category', 'submitted_by'] - search_fields = ['name'] + prepopulated_fields = {"slug": ("name",)} + raw_id_fields = ["category", "submitted_by"] + search_fields = ["name"] def get_list_filter(self, request): fields = list(super().get_list_filter(request)) - return fields + ['is_published'] + return fields + ["is_published"] def get_list_display(self, request): fields = list(super().get_list_display(request)) - return fields + ['show_link', 'is_published', 'featured'] + return fields + ["show_link", "is_published", "featured"] - @admin.display( - description='View on site' - ) + @admin.display(description="View on site") def show_link(self, obj): - return format_html(f'<a href="{obj.get_absolute_url()}">\U0001F517</a>') + return format_html(f'<a href="{obj.get_absolute_url()}">\U0001f517</a>') diff --git a/successstories/apps.py b/successstories/apps.py index 9eeec6668..df211e968 100644 --- a/successstories/apps.py +++ b/successstories/apps.py @@ -2,5 +2,4 @@ class SuccessstoriesAppConfig(AppConfig): - - name = 'successstories' + name = "successstories" diff --git a/successstories/factories.py b/successstories/factories.py index 8d3d9d85e..72e42da24 100644 --- a/successstories/factories.py +++ b/successstories/factories.py @@ -7,50 +7,48 @@ class StoryProvider(BaseProvider): - story_categories = [ - 'Arts', - 'Business', - 'Education', - 'Engineering', - 'Government', - 'Scientific', - 'Software Development', + "Arts", + "Business", + "Education", + "Engineering", + "Government", + "Scientific", + "Software Development", ] def story_category(self): return self.random_element(self.story_categories) + factory.Faker.add_provider(StoryProvider) class StoryCategoryFactory(DjangoModelFactory): - class Meta: model = StoryCategory - django_get_or_create = ('name',) + django_get_or_create = ("name",) - name = factory.Faker('story_category') + name = factory.Faker("story_category") class StoryFactory(DjangoModelFactory): - class Meta: model = Story - django_get_or_create = ('name',) + django_get_or_create = ("name",) category = factory.SubFactory(StoryCategoryFactory) - name = factory.LazyAttribute(lambda o: f'Success Story of {o.company_name}') - company_name = factory.Faker('company') - company_url = factory.Faker('url') - author = factory.Faker('name') - author_email = factory.Faker('email') - pull_quote = factory.Faker('sentence', nb_words=10) - content = factory.Faker('paragraph', nb_sentences=5) + name = factory.LazyAttribute(lambda o: f"Success Story of {o.company_name}") + company_name = factory.Faker("company") + company_url = factory.Faker("url") + author = factory.Faker("name") + author_email = factory.Faker("email") + pull_quote = factory.Faker("sentence", nb_words=10) + content = factory.Faker("paragraph", nb_sentences=5) is_published = True def initial_data(): return { - 'successstories': StoryFactory.create_batch(size=10) + [StoryFactory(featured=True)], + "successstories": StoryFactory.create_batch(size=10) + [StoryFactory(featured=True)], } diff --git a/successstories/forms.py b/successstories/forms.py index f623001b0..3c28e87e3 100644 --- a/successstories/forms.py +++ b/successstories/forms.py @@ -7,28 +7,19 @@ class StoryForm(ContentManageableModelForm): - pull_quote = forms.CharField(widget=forms.Textarea(attrs={'rows': 5})) + pull_quote = forms.CharField(widget=forms.Textarea(attrs={"rows": 5})) class Meta: model = Story - fields = ( - 'name', - 'company_name', - 'company_url', - 'category', - 'author', - 'author_email', - 'pull_quote', - 'content' - ) + fields = ("name", "company_name", "company_url", "category", "author", "author_email", "pull_quote", "content") labels = { - 'name': 'Story name', + "name": "Story name", } def clean_name(self): - name = self.cleaned_data.get('name') + name = self.cleaned_data.get("name") slug = slugify(name) story = Story.objects.filter(Q(name=name) | Q(slug=slug)).exclude(pk=self.instance.pk) if name is not None and story.exists(): - raise forms.ValidationError('Please use a unique name.') + raise forms.ValidationError("Please use a unique name.") return name diff --git a/successstories/managers.py b/successstories/managers.py index 400609e63..5fc4a7d55 100644 --- a/successstories/managers.py +++ b/successstories/managers.py @@ -20,11 +20,10 @@ def latest(self): class StoryManager(Manager.from_queryset(StoryQuerySet)): - def random_featured(self): # We don't just call queryset.order_by('?') because that # would kill the database. - count = self.featured().aggregate(count=Count('id'))['count'] + count = self.featured().aggregate(count=Count("id"))["count"] if count == 0: return self.get_queryset().none() random_index = random.randint(0, count - 1) diff --git a/successstories/migrations/0001_initial.py b/successstories/migrations/0001_initial.py index c6b7c699d..7c51aaa08 100644 --- a/successstories/migrations/0001_initial.py +++ b/successstories/migrations/0001_initial.py @@ -5,76 +5,110 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('companies', '0001_initial'), + ("companies", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Story', + name="Story", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('name', models.CharField(max_length=200)), - ('slug', models.SlugField(unique=True)), - ('company_name', models.CharField(max_length=500)), - ('company_url', models.URLField()), - ('author', models.CharField(max_length=500)), - ('pull_quote', models.TextField()), - ('content', markupfield.fields.MarkupField(rendered_field=True)), - ('content_markup_type', models.CharField(max_length=30, choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], default='restructuredtext')), - ('is_published', models.BooleanField(db_index=True, default=False)), - ('_content_rendered', models.TextField(editable=False)), - ('featured', models.BooleanField(help_text='Set to use story in the supernav', default=False)), - ('weight', models.IntegerField(help_text='Percentage weight given to display, enter 11 for 11% of views. Warnings will be given in flash messages if total of featured Stories is not equal to 100%', default=0)), - ('image', models.ImageField(upload_to='successstories', blank=True, null=True)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(db_index=True, default=django.utils.timezone.now, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("name", models.CharField(max_length=200)), + ("slug", models.SlugField(unique=True)), + ("company_name", models.CharField(max_length=500)), + ("company_url", models.URLField()), + ("author", models.CharField(max_length=500)), + ("pull_quote", models.TextField()), + ("content", markupfield.fields.MarkupField(rendered_field=True)), + ( + "content_markup_type", + models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + default="restructuredtext", + ), + ), + ("is_published", models.BooleanField(db_index=True, default=False)), + ("_content_rendered", models.TextField(editable=False)), + ("featured", models.BooleanField(help_text="Set to use story in the supernav", default=False)), + ( + "weight", + models.IntegerField( + help_text="Percentage weight given to display, enter 11 for 11% of views. Warnings will be given in flash messages if total of featured Stories is not equal to 100%", + default=0, + ), + ), + ("image", models.ImageField(upload_to="successstories", blank=True, null=True)), ], options={ - 'verbose_name': 'story', - 'verbose_name_plural': 'stories', - 'ordering': ('-created',), + "verbose_name": "story", + "verbose_name_plural": "stories", + "ordering": ("-created",), }, bases=(models.Model,), ), migrations.CreateModel( - name='StoryCategory', + name="StoryCategory", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200)), - ('slug', models.SlugField(unique=True)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=200)), + ("slug", models.SlugField(unique=True)), ], options={ - 'verbose_name': 'story category', - 'verbose_name_plural': 'story categories', - 'ordering': ('name',), + "verbose_name": "story category", + "verbose_name_plural": "story categories", + "ordering": ("name",), }, bases=(models.Model,), ), migrations.AddField( - model_name='story', - name='category', - field=models.ForeignKey(to='successstories.StoryCategory', related_name='success_stories', on_delete=models.CASCADE), + model_name="story", + name="category", + field=models.ForeignKey( + to="successstories.StoryCategory", related_name="success_stories", on_delete=models.CASCADE + ), preserve_default=True, ), migrations.AddField( - model_name='story', - name='company', - field=models.ForeignKey(null=True, to='companies.Company', related_name='success_stories', blank=True, on_delete=models.CASCADE), + model_name="story", + name="company", + field=models.ForeignKey( + null=True, to="companies.Company", related_name="success_stories", blank=True, on_delete=models.CASCADE + ), preserve_default=True, ), migrations.AddField( - model_name='story', - name='creator', - field=models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='successstories_story_creator', blank=True, on_delete=models.CASCADE), + model_name="story", + name="creator", + field=models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="successstories_story_creator", + blank=True, + on_delete=models.CASCADE, + ), preserve_default=True, ), migrations.AddField( - model_name='story', - name='last_modified_by', - field=models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, related_name='successstories_story_modified', blank=True, on_delete=models.CASCADE), + model_name="story", + name="last_modified_by", + field=models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + related_name="successstories_story_modified", + blank=True, + on_delete=models.CASCADE, + ), preserve_default=True, ), ] diff --git a/successstories/migrations/0002_auto_20150416_1853.py b/successstories/migrations/0002_auto_20150416_1853.py index c66e82bd1..6da8ebd00 100644 --- a/successstories/migrations/0002_auto_20150416_1853.py +++ b/successstories/migrations/0002_auto_20150416_1853.py @@ -2,16 +2,25 @@ class Migration(migrations.Migration): - dependencies = [ - ('successstories', '0001_initial'), + ("successstories", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='story', - name='content_markup_type', - field=models.CharField(max_length=30, default='restructuredtext', choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')]), + model_name="story", + name="content_markup_type", + field=models.CharField( + max_length=30, + default="restructuredtext", + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + ), preserve_default=True, ), ] diff --git a/successstories/migrations/0003_auto_20170720_1655.py b/successstories/migrations/0003_auto_20170720_1655.py index f1c6a3c9d..46699fa53 100644 --- a/successstories/migrations/0003_auto_20170720_1655.py +++ b/successstories/migrations/0003_auto_20170720_1655.py @@ -2,22 +2,21 @@ class Migration(migrations.Migration): - dependencies = [ - ('successstories', '0002_auto_20150416_1853'), + ("successstories", "0002_auto_20150416_1853"), ] operations = [ migrations.AddField( - model_name='story', - name='author_email', + model_name="story", + name="author_email", field=models.EmailField(blank=True, max_length=100, null=True), preserve_default=True, ), migrations.AlterField( - model_name='story', - name='author', - field=models.CharField(max_length=500, help_text='Author of the content'), + model_name="story", + name="author", + field=models.CharField(max_length=500, help_text="Author of the content"), preserve_default=True, ), ] diff --git a/successstories/migrations/0004_auto_20170724_0507.py b/successstories/migrations/0004_auto_20170724_0507.py index e10210d2a..788d7de0c 100644 --- a/successstories/migrations/0004_auto_20170724_0507.py +++ b/successstories/migrations/0004_auto_20170724_0507.py @@ -2,16 +2,15 @@ class Migration(migrations.Migration): - dependencies = [ - ('successstories', '0003_auto_20170720_1655'), + ("successstories", "0003_auto_20170720_1655"), ] operations = [ migrations.AlterField( - model_name='story', - name='company_url', - field=models.URLField(verbose_name='Company URL'), + model_name="story", + name="company_url", + field=models.URLField(verbose_name="Company URL"), preserve_default=True, ), ] diff --git a/successstories/migrations/0005_auto_20170726_0645.py b/successstories/migrations/0005_auto_20170726_0645.py index 0a23151e5..63b9b160f 100644 --- a/successstories/migrations/0005_auto_20170726_0645.py +++ b/successstories/migrations/0005_auto_20170726_0645.py @@ -2,16 +2,15 @@ class Migration(migrations.Migration): - dependencies = [ - ('successstories', '0004_auto_20170724_0507'), + ("successstories", "0004_auto_20170724_0507"), ] operations = [ migrations.AlterField( - model_name='story', - name='name', - field=models.CharField(max_length=200, help_text='Title of your success story'), + model_name="story", + name="name", + field=models.CharField(max_length=200, help_text="Title of your success story"), preserve_default=True, ), ] diff --git a/successstories/migrations/0006_auto_20170726_0824.py b/successstories/migrations/0006_auto_20170726_0824.py index 10f6cbd1c..6d9a4617c 100644 --- a/successstories/migrations/0006_auto_20170726_0824.py +++ b/successstories/migrations/0006_auto_20170726_0824.py @@ -4,85 +4,80 @@ from successstories.utils import get_field_list, convert_to_datetime -MARKER = '.. Migrated from Pages model.\n\n' -DEFAULT_URL = 'https://www.python.org/' +MARKER = ".. Migrated from Pages model.\n\n" +DEFAULT_URL = "https://www.python.org/" normalized_company_names = { - 'dlink': 'D-Link', - 'astra': 'AstraZeneca', - 'bats': 'BATS', - 'carmanah': 'Carmanah Technologies Inc.', - 'devnet': 'DevNet', - 'esr': 'ESR', - 'ezro': 'devIS', - 'forecastwatch': 'ForecastWatch.com', - 'gravityzoo': 'GravityZoo', - 'gusto': 'Gusto', - 'ilm': 'ILM', - 'loveintros': 'LoveIntros', - 'mayavi': 'MayaVi', - 'mmtk': 'MMTK', - 'natsworld': 'Nat\'s World', - 'projectpipe': 'ProjectPipe', - 'resolver': 'Resolver Systems', - 'siena': 'Siena Technology Ltd.', - 'st-andrews': 'University of St Andrews', - 'tempest': 'TEMPEST', - 'testgo': 'Test&Go', - 'tribon': 'Tribon Solutions', - 'tttech': 'TTTech', - 'usa': 'USA', - 'wingide': 'Wing IDE', - 'wordstream': 'WordStream', - 'xist': 'XIST', + "dlink": "D-Link", + "astra": "AstraZeneca", + "bats": "BATS", + "carmanah": "Carmanah Technologies Inc.", + "devnet": "DevNet", + "esr": "ESR", + "ezro": "devIS", + "forecastwatch": "ForecastWatch.com", + "gravityzoo": "GravityZoo", + "gusto": "Gusto", + "ilm": "ILM", + "loveintros": "LoveIntros", + "mayavi": "MayaVi", + "mmtk": "MMTK", + "natsworld": "Nat's World", + "projectpipe": "ProjectPipe", + "resolver": "Resolver Systems", + "siena": "Siena Technology Ltd.", + "st-andrews": "University of St Andrews", + "tempest": "TEMPEST", + "testgo": "Test&Go", + "tribon": "Tribon Solutions", + "tttech": "TTTech", + "usa": "USA", + "wingide": "Wing IDE", + "wordstream": "WordStream", + "xist": "XIST", } fix_category_names = { - 'Software Devleopment': 'Software Development', - 'Science': 'Scientific', + "Software Devleopment": "Software Development", + "Science": "Scientific", } def migrate_old_content(apps, schema_editor): - Page = apps.get_model('pages', 'Page') - Story = apps.get_model('successstories', 'Story') - StoryCategory = apps.get_model('successstories', 'StoryCategory') + Page = apps.get_model("pages", "Page") + Story = apps.get_model("successstories", "Story") + StoryCategory = apps.get_model("successstories", "StoryCategory") db_alias = schema_editor.connection.alias pages = Page.objects.using(db_alias).filter( - path__startswith='about/success/', - content_markup_type='restructuredtext' + path__startswith="about/success/", content_markup_type="restructuredtext" ) stories = [] for page in pages.iterator(): field_list = dict(get_field_list(page.content.raw)) - extract_company_name = page.path.split('/')[-1] - company_name = normalized_company_names.get( - extract_company_name.lower(), extract_company_name.title() - ) + extract_company_name = page.path.split("/")[-1] + company_name = normalized_company_names.get(extract_company_name.lower(), extract_company_name.title()) company_slug = slugify(company_name) check_story = Story.objects.filter(slug=company_slug).exists() if check_story: # Move to the next one if story is already in the table. continue - company_url = field_list.get('website', - field_list.get('web site', DEFAULT_URL)) - category_cleaned = field_list['category'].strip().split(',')[0].strip() - category_cleaned = fix_category_names.get(category_cleaned, - category_cleaned) + company_url = field_list.get("website", field_list.get("web site", DEFAULT_URL)) + category_cleaned = field_list["category"].strip().split(",")[0].strip() + category_cleaned = fix_category_names.get(category_cleaned, category_cleaned) category, _ = StoryCategory.objects.get_or_create( name=category_cleaned, defaults={ - 'slug': slugify(category_cleaned), - } + "slug": slugify(category_cleaned), + }, ) story = Story( - name=field_list['title'], + name=field_list["title"], slug=company_slug, - created=convert_to_datetime(field_list['date']), + created=convert_to_datetime(field_list["date"]), company_name=company_name, company_url=company_url, category=category, - author=field_list['author'], - pull_quote=field_list['summary'], + author=field_list["author"], + pull_quote=field_list["summary"], content=MARKER + page.content.raw, is_published=True, updated=now(), @@ -92,18 +87,17 @@ def migrate_old_content(apps, schema_editor): def delete_migrated_content(apps, schema_editor): - Story = apps.get_model('successstories', 'Story') + Story = apps.get_model("successstories", "Story") db_alias = schema_editor.connection.alias Story.objects.using(db_alias).filter(content__startswith=MARKER).delete() class Migration(migrations.Migration): - dependencies = [ - ('successstories', '0005_auto_20170726_0645'), + ("successstories", "0005_auto_20170726_0645"), # Added dependency to enable using models from pages # in migrate_old_content. - ('pages', '0002_auto_20150416_1853'), + ("pages", "0002_auto_20150416_1853"), ] operations = [ diff --git a/successstories/migrations/0007_remove_story_weight.py b/successstories/migrations/0007_remove_story_weight.py index e25e2ea47..184cf2f8b 100644 --- a/successstories/migrations/0007_remove_story_weight.py +++ b/successstories/migrations/0007_remove_story_weight.py @@ -2,14 +2,13 @@ class Migration(migrations.Migration): - dependencies = [ - ('successstories', '0006_auto_20170726_0824'), + ("successstories", "0006_auto_20170726_0824"), ] operations = [ migrations.RemoveField( - model_name='story', - name='weight', + model_name="story", + name="weight", ), ] diff --git a/successstories/migrations/0008_auto_20170821_2000.py b/successstories/migrations/0008_auto_20170821_2000.py index d06c027ba..d0e6fb025 100644 --- a/successstories/migrations/0008_auto_20170821_2000.py +++ b/successstories/migrations/0008_auto_20170821_2000.py @@ -2,15 +2,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('successstories', '0007_remove_story_weight'), + ("successstories", "0007_remove_story_weight"), ] operations = [ migrations.AlterField( - model_name='story', - name='name', + model_name="story", + name="name", field=models.CharField(max_length=200), ), ] diff --git a/successstories/migrations/0009_auto_20180705_0352.py b/successstories/migrations/0009_auto_20180705_0352.py index fb3067b8e..5a644543d 100644 --- a/successstories/migrations/0009_auto_20180705_0352.py +++ b/successstories/migrations/0009_auto_20180705_0352.py @@ -4,20 +4,19 @@ class Migration(migrations.Migration): - dependencies = [ - ('successstories', '0008_auto_20170821_2000'), + ("successstories", "0008_auto_20170821_2000"), ] operations = [ migrations.AlterField( - model_name='story', - name='slug', + model_name="story", + name="slug", field=models.SlugField(max_length=200, unique=True), ), migrations.AlterField( - model_name='storycategory', - name='slug', + model_name="storycategory", + name="slug", field=models.SlugField(max_length=200, unique=True), ), ] diff --git a/successstories/migrations/0010_story_submitted_by.py b/successstories/migrations/0010_story_submitted_by.py index 79b12beb4..e37aaaeab 100644 --- a/successstories/migrations/0010_story_submitted_by.py +++ b/successstories/migrations/0010_story_submitted_by.py @@ -6,16 +6,17 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('successstories', '0009_auto_20180705_0352'), + ("successstories", "0009_auto_20180705_0352"), ] operations = [ migrations.AddField( - model_name='story', - name='submitted_by', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + model_name="story", + name="submitted_by", + field=models.ForeignKey( + null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL + ), ), ] diff --git a/successstories/migrations/0011_auto_20220127_1923.py b/successstories/migrations/0011_auto_20220127_1923.py index 25f0a7009..eb9dade3c 100644 --- a/successstories/migrations/0011_auto_20220127_1923.py +++ b/successstories/migrations/0011_auto_20220127_1923.py @@ -6,15 +6,16 @@ class Migration(migrations.Migration): - dependencies = [ - ('successstories', '0010_story_submitted_by'), + ("successstories", "0010_story_submitted_by"), ] operations = [ migrations.AlterField( - model_name='story', - name='submitted_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + model_name="story", + name="submitted_by", + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL + ), ), ] diff --git a/successstories/migrations/0012_alter_story_creator_alter_story_last_modified_by.py b/successstories/migrations/0012_alter_story_creator_alter_story_last_modified_by.py index dee246421..e5e082cf3 100644 --- a/successstories/migrations/0012_alter_story_creator_alter_story_last_modified_by.py +++ b/successstories/migrations/0012_alter_story_creator_alter_story_last_modified_by.py @@ -6,21 +6,32 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('successstories', '0011_auto_20220127_1923'), + ("successstories", "0011_auto_20220127_1923"), ] operations = [ migrations.AlterField( - model_name='story', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="story", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='story', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="story", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/successstories/models.py b/successstories/models.py index e5345b435..f0559dbb7 100644 --- a/successstories/models.py +++ b/successstories/models.py @@ -17,67 +17,66 @@ from fastly.utils import purge_url -PSF_TO_EMAILS = ['psf-staff@python.org'] -DEFAULT_MARKUP_TYPE = getattr(settings, 'DEFAULT_MARKUP_TYPE', 'restructuredtext') +PSF_TO_EMAILS = ["psf-staff@python.org"] +DEFAULT_MARKUP_TYPE = getattr(settings, "DEFAULT_MARKUP_TYPE", "restructuredtext") class StoryCategory(NameSlugModel): - class Meta: - ordering = ('name',) - verbose_name = 'story category' - verbose_name_plural = 'story categories' + ordering = ("name",) + verbose_name = "story category" + verbose_name_plural = "story categories" def __str__(self): return self.name def get_absolute_url(self): - return reverse('success_story_list_category', kwargs={'slug': self.slug}) + return reverse("success_story_list_category", kwargs={"slug": self.slug}) class Story(NameSlugModel, ContentManageable): company_name = models.CharField(max_length=500) - company_url = models.URLField(verbose_name='Company URL') + company_url = models.URLField(verbose_name="Company URL") company = models.ForeignKey( Company, - related_name='success_stories', + related_name="success_stories", blank=True, null=True, on_delete=models.CASCADE, ) category = models.ForeignKey( StoryCategory, - related_name='success_stories', + related_name="success_stories", on_delete=models.CASCADE, ) - author = models.CharField(max_length=500, help_text='Author of the content') + author = models.CharField(max_length=500, help_text="Author of the content") author_email = models.EmailField(max_length=100, blank=True, null=True) pull_quote = models.TextField() content = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE) is_published = models.BooleanField(default=False, db_index=True) featured = models.BooleanField(default=False, help_text="Set to use story in the supernav") - image = models.ImageField(upload_to='successstories', blank=True, null=True) + image = models.ImageField(upload_to="successstories", blank=True, null=True) submitted_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL) objects = StoryManager() class Meta: - ordering = ('-created',) - verbose_name = 'story' - verbose_name_plural = 'stories' + ordering = ("-created",) + verbose_name = "story" + verbose_name_plural = "stories" def __str__(self): return self.name def get_absolute_url(self): - return reverse('success_story_detail', kwargs={'slug': self.slug}) + return reverse("success_story_detail", kwargs={"slug": self.slug}) def get_admin_url(self): - return reverse('admin:successstories_story_change', args=(self.id,)) + return reverse("admin:successstories_story_change", args=(self.id,)) def get_company_name(self): - """ Return company name depending on ForeignKey """ + """Return company name depending on ForeignKey""" if self.company: return self.company.name else: @@ -92,26 +91,29 @@ def get_company_url(self): @receiver(post_save, sender=Story) def update_successstories_supernav(sender, instance, created, **kwargs): - """ Update download supernav """ + """Update download supernav""" # Skip in fixtures - if kwargs.get('raw', False): + if kwargs.get("raw", False): return if instance.is_published and instance.featured: - content = render_to_string('successstories/supernav.html', { - 'story': instance, - }) + content = render_to_string( + "successstories/supernav.html", + { + "story": instance, + }, + ) box, _ = Box.objects.update_or_create( - label='supernav-python-success-stories', + label="supernav-python-success-stories", defaults={ - 'content': content, - 'content_markup_type': 'html', - } + "content": content, + "content_markup_type": "html", + }, ) # Purge Fastly cache - purge_url('/box/supernav-python-success-stories/') + purge_url("/box/supernav-python-success-stories/") if instance.is_published: # Purge the page itself @@ -121,7 +123,7 @@ def update_successstories_supernav(sender, instance, created, **kwargs): @receiver(post_save, sender=Story) def send_email_to_psf(sender, instance, created, **kwargs): # Skip in fixtures - if kwargs.get('raw', False) or not created: + if kwargs.get("raw", False) or not created: return if not instance.is_published: @@ -145,7 +147,7 @@ def send_email_to_psf(sender, instance, created, **kwargs): name_lines = instance.name.splitlines() name = name_lines[0] if name_lines else instance.name email = EmailMessage( - f'New success story submission: {name}', + f"New success story submission: {name}", body.format( name=instance.name, company_name=instance.company_name, @@ -155,9 +157,7 @@ def send_email_to_psf(sender, instance, created, **kwargs): author_email=instance.author_email, pull_quote=instance.pull_quote, content=instance.content.raw, - admin_url='https://{}{}'.format( - Site.objects.get_current(), instance.get_admin_url() - ), + admin_url="https://{}{}".format(Site.objects.get_current(), instance.get_admin_url()), ).strip(), settings.DEFAULT_FROM_EMAIL, PSF_TO_EMAILS, diff --git a/successstories/tests/test_forms.py b/successstories/tests/test_forms.py index d4bb535cc..4cb2fc543 100644 --- a/successstories/tests/test_forms.py +++ b/successstories/tests/test_forms.py @@ -5,17 +5,16 @@ class StoryFormTests(TestCase): - def test_duplicate_name(self): category = StoryCategoryFactory() data = { - 'name': 'Swedish Death Metal', - 'company_name': 'Dark Tranquillity', - 'company_url': 'https://twitter.com/dtofficial', - 'category': category.pk, - 'author': 'Mikael Stanne', - 'pull_quote': 'Liver!', - 'content': 'Spam eggs', + "name": "Swedish Death Metal", + "company_name": "Dark Tranquillity", + "company_url": "https://twitter.com/dtofficial", + "category": category.pk, + "author": "Mikael Stanne", + "pull_quote": "Liver!", + "content": "Spam eggs", } form = StoryForm(data=data) self.assertTrue(form.is_valid()) @@ -25,32 +24,26 @@ def test_duplicate_name(self): form2 = StoryForm(data=data) self.assertFalse(form2.is_valid()) - self.assertEqual( - form2.errors, - {'name': ['Please use a unique name.']} - ) + self.assertEqual(form2.errors, {"name": ["Please use a unique name."]}) def test_author_email(self): category = StoryCategoryFactory() data = { - 'name': 'Swedish Death Metal', - 'company_name': 'Dark Tranquillity', - 'company_url': 'https://twitter.com/dtofficial', - 'category': category.pk, - 'author': 'Mikael Stanne', - 'author_email': 'stanne@dtofficial.se', - 'pull_quote': 'Liver!', - 'content': 'Spam eggs', + "name": "Swedish Death Metal", + "company_name": "Dark Tranquillity", + "company_url": "https://twitter.com/dtofficial", + "category": category.pk, + "author": "Mikael Stanne", + "author_email": "stanne@dtofficial.se", + "pull_quote": "Liver!", + "content": "Spam eggs", } form = StoryForm(data=data) self.assertTrue(form.is_valid()) self.assertEqual(form.errors, {}) data_invalid_email = data.copy() - data_invalid_email['author_email'] = 'stanneinvalid' + data_invalid_email["author_email"] = "stanneinvalid" form2 = StoryForm(data=data_invalid_email) self.assertFalse(form2.is_valid()) - self.assertEqual( - form2.errors, - {'author_email': ['Enter a valid email address.']} - ) + self.assertEqual(form2.errors, {"author_email": ["Enter a valid email address."]}) diff --git a/successstories/tests/test_models.py b/successstories/tests/test_models.py index 418d27062..806a9511c 100644 --- a/successstories/tests/test_models.py +++ b/successstories/tests/test_models.py @@ -8,21 +8,20 @@ class StoryModelTests(TestCase): def setUp(self): self.category = StoryCategoryFactory() self.story1 = StoryFactory(category=self.category) - self.story2 = StoryFactory(name='Fraft Story', category=self.category, is_published=False) - self.story3 = StoryFactory(name='Featured Story', category=self.category, featured=True) + self.story2 = StoryFactory(name="Fraft Story", category=self.category, is_published=False) + self.story3 = StoryFactory(name="Featured Story", category=self.category, featured=True) def test_published(self): self.assertEqual(len(Story.objects.published()), 2) def test_draft(self): draft_stories = Story.objects.draft() - self.assertTrue(all(story.name == 'Fraft Story' for story in draft_stories)) + self.assertTrue(all(story.name == "Fraft Story" for story in draft_stories)) def test_featured(self): featured_stories = Story.objects.featured() - expected_repr = [f'<Story: {self.story3.name}>'] + expected_repr = [f"<Story: {self.story3.name}>"] self.assertQuerysetEqual(featured_stories, expected_repr, transform=repr) def test_get_admin_url(self): - self.assertEqual(self.story1.get_admin_url(), - '/admin/successstories/story/%d/change/' % self.story1.pk) + self.assertEqual(self.story1.get_admin_url(), "/admin/successstories/story/%d/change/" % self.story1.pk) diff --git a/successstories/tests/test_templatetags.py b/successstories/tests/test_templatetags.py index 88d89c85d..b18a362ba 100644 --- a/successstories/tests/test_templatetags.py +++ b/successstories/tests/test_templatetags.py @@ -6,7 +6,7 @@ class StoryTemplateTagTests(TestCase): def setUp(self): - self.category = StoryCategoryFactory(name='Arts') + self.category = StoryCategoryFactory(name="Arts") self.story1 = StoryFactory(category=self.category, featured=True) self.story2 = StoryFactory(category=self.category, is_published=False) @@ -15,21 +15,29 @@ def render(self, tmpl, **context): return t.render(template.Context(context)) def test_get_story_categories(self): - r = self.render('{% load successstories %}{% get_story_categories as story_categories %}{% for category in story_categories %}{{ category }}{% endfor %}') + r = self.render( + "{% load successstories %}{% get_story_categories as story_categories %}{% for category in story_categories %}{{ category }}{% endfor %}" + ) self.assertEqual(r, self.category.name) def test_get_stories_latest(self): - r = self.render('{% load successstories %}{% get_stories_latest as stories %}{% for story in stories %}{{ story }}{% endfor %}') + r = self.render( + "{% load successstories %}{% get_stories_latest as stories %}{% for story in stories %}{{ story }}{% endfor %}" + ) self.assertEqual(r, self.story1.name) def test_get_stories_by_category(self): - r = self.render('{% load successstories %}{% get_stories_by_category category_slug="arts" as category_stories %}{% for story in category_stories %}{{ story }}{% endfor %}') + r = self.render( + '{% load successstories %}{% get_stories_by_category category_slug="arts" as category_stories %}{% for story in category_stories %}{{ story }}{% endfor %}' + ) self.assertEqual(r, self.story1.name) def test_get_stories_by_category_invalid(self): - r = self.render('{% load successstories %}{% get_stories_by_category category_slug="poop" as category_stories %}{% for story in category_stories %}{{ story }}{% endfor %}') - self.assertEqual(r, '') + r = self.render( + '{% load successstories %}{% get_stories_by_category category_slug="poop" as category_stories %}{% for story in category_stories %}{{ story }}{% endfor %}' + ) + self.assertEqual(r, "") def test_get_featured_story(self): - r = self.render('{% load successstories %}{% get_featured_story as story %}{{ story }}') + r = self.render("{% load successstories %}{% get_featured_story as story %}{{ story }}") self.assertEqual(r, self.story1.name) diff --git a/successstories/tests/test_utils.py b/successstories/tests/test_utils.py index f2b659ddd..69943ad14 100644 --- a/successstories/tests/test_utils.py +++ b/successstories/tests/test_utils.py @@ -6,16 +6,15 @@ class UtilsTestCase(SimpleTestCase): - def test_convert_to_datetime(self): tests = [ - ('%Y-%m-%d %H:%M:%S', '2017-02-24 21:05:24'), - ('%Y-%m-%d', '2017-02-24'), + ("%Y-%m-%d %H:%M:%S", "2017-02-24 21:05:24"), + ("%Y-%m-%d", "2017-02-24"), ] for fmt, string in tests: with self.subTest(fmt=fmt): self.assertIsInstance(convert_to_datetime(string), datetime.datetime) - self.assertIsNone(convert_to_datetime('invalid')) + self.assertIsNone(convert_to_datetime("invalid")) def test_get_field_list(self): source = """\ @@ -27,7 +26,4 @@ def test_get_field_list(self): Baz baz """ - self.assertEqual( - list(get_field_list(source)), - [('spam', 'Eggs'), ('author', 'Guido'), ('date', '2017-02-24')] - ) + self.assertEqual(list(get_field_list(source)), [("spam", "Eggs"), ("author", "Guido"), ("date", "2017-02-24")]) diff --git a/successstories/tests/test_views.py b/successstories/tests/test_views.py index 62f478ccc..99a0f26ba 100644 --- a/successstories/tests/test_views.py +++ b/successstories/tests/test_views.py @@ -15,65 +15,65 @@ class StoryViewTests(TestCase): def setUp(self): - self.user = UserFactory(username='username', password='password') - self.category = StoryCategoryFactory(name='Arts') + self.user = UserFactory(username="username", password="password") + self.category = StoryCategoryFactory(name="Arts") self.story1 = StoryFactory(category=self.category, featured=True) self.story2 = StoryFactory(category=self.category, is_published=False) def test_story_view(self): - url = reverse('success_story_detail', kwargs={'slug': self.story1.slug}) + url = reverse("success_story_detail", kwargs={"slug": self.story1.slug}) r = self.client.get(url) self.assertEqual(r.status_code, 200) - self.assertEqual(r.context['story'].pk, self.story1.pk) - self.assertEqual(len(r.context['category_list']), 1) + self.assertEqual(r.context["story"].pk, self.story1.pk) + self.assertEqual(len(r.context["category_list"]), 1) def test_unpublished_story_view(self): - url = reverse('success_story_detail', kwargs={'slug': self.story2.slug}) + url = reverse("success_story_detail", kwargs={"slug": self.story2.slug}) r = self.client.get(url) self.assertEqual(r.status_code, 404) # Staffs can see an unpublished story. staff = User.objects.create_superuser( - username='spameggs', - password='password', - email='superuser@example.com', + username="spameggs", + password="password", + email="superuser@example.com", ) self.assertTrue(staff.is_staff) - self.client.login(username=staff.username, password='password') + self.client.login(username=staff.username, password="password") r = self.client.get(url) self.assertEqual(r.status_code, 200) - self.assertFalse(r.context['story'].is_published) + self.assertFalse(r.context["story"].is_published) def test_story_list(self): - url = reverse('success_story_list') + url = reverse("success_story_list") r = self.client.get(url) self.assertEqual(r.status_code, 200) - self.assertEqual(len(r.context['stories']), 1) + self.assertEqual(len(r.context["stories"]), 1) def test_story_category_list(self): - url = reverse('success_story_list_category', kwargs={'slug': self.category.slug}) + url = reverse("success_story_list_category", kwargs={"slug": self.category.slug}) r = self.client.get(url) self.assertEqual(r.status_code, 200) - self.assertEqual(r.context['object'], self.category) - self.assertEqual(len(r.context['object'].success_stories.all()), 2) - self.assertEqual(r.context['object'].success_stories.all()[0].pk, self.story2.pk) + self.assertEqual(r.context["object"], self.category) + self.assertEqual(len(r.context["object"].success_stories.all()), 2) + self.assertEqual(r.context["object"].success_stories.all()[0].pk, self.story2.pk) def test_story_create(self): mail.outbox = [] - url = reverse('success_story_create') - self.client.login(username='username', password='password') + url = reverse("success_story_create") + self.client.login(username="username", password="password") response = self.client.get(url) self.assertEqual(response.status_code, 200) post_data = { - 'name': 'Three', - 'company_name': 'Company Three', - 'company_url': 'http://djangopony.com/', - 'category': self.category.pk, - 'author': 'Kevin Arnold', - 'author_email': 'kevin@arnold.com', - 'pull_quote': 'Liver!', - 'content': 'Growing up is never easy.\n\nFoo bar baz.\n', + "name": "Three", + "company_name": "Company Three", + "company_url": "http://djangopony.com/", + "category": self.category.pk, + "author": "Kevin Arnold", + "author_email": "kevin@arnold.com", + "pull_quote": "Liver!", + "content": "Growing up is never easy.\n\nFoo bar baz.\n", settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, } @@ -82,35 +82,32 @@ def test_story_create(self): self.assertRedirects(response, url) self.assertEqual(len(mail.outbox), 1) - self.assertEqual( - mail.outbox[0].subject, - 'New success story submission: {}'.format(post_data['name']) - ) + self.assertEqual(mail.outbox[0].subject, "New success story submission: {}".format(post_data["name"])) expected_output = re.compile( - r'Name: (.*)\n' - r'Company name: (.*)\n' - r'Company URL: (.*)\n' - r'Category: (.*)\n' - r'Author: (.*)\n' - r'Author email: (.*)\n' - r'Pull quote:\n' - r'\n' - r'(.*)\n' - r'\n' - r'Content:\n' - r'\n' - r'(.*)\n' - r'\n' - r'Review URL: (.*)', - flags=re.DOTALL + r"Name: (.*)\n" + r"Company name: (.*)\n" + r"Company URL: (.*)\n" + r"Category: (.*)\n" + r"Author: (.*)\n" + r"Author email: (.*)\n" + r"Pull quote:\n" + r"\n" + r"(.*)\n" + r"\n" + r"Content:\n" + r"\n" + r"(.*)\n" + r"\n" + r"Review URL: (.*)", + flags=re.DOTALL, ) self.assertRegex(mail.outbox[0].body, expected_output) # 'content' field should be in reST format so just check that # body of the email doesn't contain any HTML tags. - self.assertNotIn('<p>', mail.outbox[0].body) - self.assertEqual(mail.outbox[0].content_subtype, 'plain') - self.assertEqual(mail.outbox[0].reply_to, [post_data['author_email']]) - stories = Story.objects.draft().filter(slug__exact='three') + self.assertNotIn("<p>", mail.outbox[0].body) + self.assertEqual(mail.outbox[0].content_subtype, "plain") + self.assertEqual(mail.outbox[0].reply_to, [post_data["author_email"]]) + stories = Story.objects.draft().filter(slug__exact="three") self.assertEqual(len(stories), 1) story = stories[0] @@ -121,66 +118,63 @@ def test_story_create(self): response = self.client.post(url, post_data) self.assertEqual(response.status_code, 200) - self.assertContains(response, 'Please use a unique name.') + self.assertContains(response, "Please use a unique name.") del mail.outbox[:] def test_story_multiline_email_subject(self): mail.outbox = [] - url = reverse('success_story_create') + url = reverse("success_story_create") post_data = { - 'name': 'First line\nSecond line', - 'company_name': 'Company Three', - 'company_url': 'http://djangopony.com/', - 'category': self.category.pk, - 'author': 'Kevin Arnold', - 'author_email': 'kevin@arnold.com', - 'pull_quote': 'Liver!', - 'content': 'Growing up is never easy.\n\nFoo bar baz.\n', + "name": "First line\nSecond line", + "company_name": "Company Three", + "company_url": "http://djangopony.com/", + "category": self.category.pk, + "author": "Kevin Arnold", + "author_email": "kevin@arnold.com", + "pull_quote": "Liver!", + "content": "Growing up is never easy.\n\nFoo bar baz.\n", settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, } - self.client.login(username='username', password='password') + self.client.login(username="username", password="password") response = self.client.post(url, post_data) self.assertEqual(response.status_code, 302) self.assertRedirects(response, url) self.assertEqual(len(mail.outbox), 1) - self.assertEqual( - mail.outbox[0].subject, - 'New success story submission: First line' - ) - self.assertNotIn('Second line', mail.outbox[0].subject) + self.assertEqual(mail.outbox[0].subject, "New success story submission: First line") + self.assertNotIn("Second line", mail.outbox[0].subject) del mail.outbox[:] def test_story_duplicate_slug(self): - url = reverse('success_story_create') + url = reverse("success_story_create") post_data = { - 'name': 'r87comwwwpythonorg', - 'company_name': 'Company Three', - 'company_url': 'http://djangopony.com/', - 'category': self.category.pk, - 'author': 'Kevin Arnold', - 'author_email': 'kevin@arnold.com', - 'pull_quote': 'Liver!', - 'content': 'Growing up is never easy.\n\nFoo bar baz.\n', + "name": "r87comwwwpythonorg", + "company_name": "Company Three", + "company_url": "http://djangopony.com/", + "category": self.category.pk, + "author": "Kevin Arnold", + "author_email": "kevin@arnold.com", + "pull_quote": "Liver!", + "content": "Growing up is never easy.\n\nFoo bar baz.\n", settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, } - self.client.login(username='username', password='password') + self.client.login(username="username", password="password") response = self.client.post(url, post_data) self.assertEqual(response.status_code, 302) self.assertRedirects(response, url) post_data = post_data.copy() - post_data['name'] = '///r87.com/?www.python.org/' + post_data["name"] = "///r87.com/?www.python.org/" response = self.client.post(url, post_data) self.assertEqual(response.status_code, 200) - self.assertContains(response, 'Please use a unique name.') + self.assertContains(response, "Please use a unique name.") def test_slug_field_max_length(self): # name and slug fields come from NameSlugModel and their max_length @@ -188,21 +182,21 @@ def test_slug_field_max_length(self): # 50 and since we set CharField.max_length to 200, we have to update # SlugField.max_length as well. This was found by Netsparker and # recorded by Sentry. See PYDOTORG-PROD-23 for details. - url = reverse('success_story_create') + url = reverse("success_story_create") post_data = { - 'name': '|nslookup${IFS}"vprlkb-tutkaenivhxr1i4bxrdosuteo8wh4mb2r""cys.r87.me"', - 'company_name': 'Company Three', - 'company_url': 'http://djangopony.com/', - 'category': self.category.pk, - 'author': 'Kevin Arnold', - 'author_email': 'kevin@arnold.com', - 'pull_quote': 'Liver!', - 'content': 'Growing up is never easy.\n\nFoo bar baz.\n', + "name": '|nslookup${IFS}"vprlkb-tutkaenivhxr1i4bxrdosuteo8wh4mb2r""cys.r87.me"', + "company_name": "Company Three", + "company_url": "http://djangopony.com/", + "category": self.category.pk, + "author": "Kevin Arnold", + "author_email": "kevin@arnold.com", + "pull_quote": "Liver!", + "content": "Growing up is never easy.\n\nFoo bar baz.\n", settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, } - self.client.login(username='username', password='password') + self.client.login(username="username", password="password") response = self.client.post(url, post_data) self.assertEqual(response.status_code, 302) self.assertRedirects(response, url) @@ -211,21 +205,21 @@ def test_nul_character(self): # This was originally reported by Sentry (PYDOTORG-PROD-21, # PYDOTORG-PROD-25) and fixed in Django 2.0 by adding # ProhibitNullCharactersValidator validator. - url = reverse('success_story_create') + url = reverse("success_story_create") post_data = { - 'name': 'Before\0After', - 'company_name': 'Company Three', - 'company_url': 'http://djangopony.com/', - 'category': self.category.pk, - 'author': 'Kevin Arnold', - 'author_email': 'kevin@arnold.com', - 'pull_quote': 'Liver!', - 'content': 'Growing up is never easy.\n\nFoo bar baz.\n', + "name": "Before\0After", + "company_name": "Company Three", + "company_url": "http://djangopony.com/", + "category": self.category.pk, + "author": "Kevin Arnold", + "author_email": "kevin@arnold.com", + "pull_quote": "Liver!", + "content": "Growing up is never easy.\n\nFoo bar baz.\n", settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, } - self.client.login(username='username', password='password') + self.client.login(username="username", password="password") response = self.client.post(url, post_data) self.assertEqual(response.status_code, 200) - self.assertContains(response, 'Null characters are not allowed.') + self.assertContains(response, "Null characters are not allowed.") diff --git a/successstories/urls.py b/successstories/urls.py index eb9a5a454..09e72c640 100644 --- a/successstories/urls.py +++ b/successstories/urls.py @@ -3,8 +3,8 @@ urlpatterns = [ - path('', views.StoryList.as_view(), name='success_story_list'), - path('create/', views.StoryCreate.as_view(), name='success_story_create'), - path('<slug:slug>/', views.StoryDetail.as_view(), name='success_story_detail'), - path('category/<slug:slug>/', views.StoryListCategory.as_view(), name='success_story_list_category'), + path("", views.StoryList.as_view(), name="success_story_list"), + path("create/", views.StoryCreate.as_view(), name="success_story_create"), + path("<slug:slug>/", views.StoryDetail.as_view(), name="success_story_detail"), + path("category/<slug:slug>/", views.StoryListCategory.as_view(), name="success_story_list_category"), ] diff --git a/successstories/utils.py b/successstories/utils.py index 25c80574c..60283f54e 100644 --- a/successstories/utils.py +++ b/successstories/utils.py @@ -17,14 +17,13 @@ def convert_to_datetime(string): formats = [ - '%Y/%m/%d %H:%M:%S', - '%Y-%m-%d %H:%M:%S', - '%Y-%m-%d', + "%Y/%m/%d %H:%M:%S", + "%Y-%m-%d %H:%M:%S", + "%Y-%m-%d", ] for fmt in formats: try: - return make_aware(datetime.datetime.strptime(string, fmt), - get_current_timezone()) + return make_aware(datetime.datetime.strptime(string, fmt), get_current_timezone()) except ValueError: continue @@ -33,9 +32,9 @@ def get_field_list(source): dom = publish_doctree(source).asdom() tree = fromstring(dom.toxml()) for field in tree.iter(): - if field.tag == 'field': - name = next(field.iter(tag='field_name')) - body = next(field.iter(tag='field_body')) - yield name.text.lower(), ''.join(body.itertext()) - elif field.tag in ('author', 'date'): - yield field.tag, ''.join(field.itertext()) + if field.tag == "field": + name = next(field.iter(tag="field_name")) + body = next(field.iter(tag="field_body")) + yield name.text.lower(), "".join(body.itertext()) + elif field.tag in ("author", "date"): + yield field.tag, "".join(field.itertext()) diff --git a/successstories/views.py b/successstories/views.py index 3a8c3542a..9201d78fd 100644 --- a/successstories/views.py +++ b/successstories/views.py @@ -11,20 +11,18 @@ class ContextMixin: - def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['category_list'] = StoryCategory.objects.all() + context["category_list"] = StoryCategory.objects.all() return context class StoryCreate(LoginRequiredMixin, ContextMixin, CreateView): model = Story form_class = StoryForm - template_name = 'successstories/story_form.html' + template_name = "successstories/story_form.html" success_message = ( - 'Your success story submission has been recorded. ' - 'It will be reviewed by the PSF staff and published.' + "Your success story submission has been recorded. " "It will be reviewed by the PSF staff and published." ) @method_decorator(check_honeypot) @@ -32,7 +30,7 @@ def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs) def get_success_url(self): - return reverse('success_story_create') + return reverse("success_story_create") def form_valid(self, form): obj = form.save(commit=False) @@ -40,9 +38,10 @@ def form_valid(self, form): messages.add_message(self.request, messages.SUCCESS, self.success_message) return super().form_valid(form) + class StoryDetail(ContextMixin, DetailView): - template_name = 'successstories/story_detail.html' - context_object_name = 'story' + template_name = "successstories/story_detail.html" + context_object_name = "story" def get_queryset(self): if self.request.user.is_staff: @@ -51,8 +50,8 @@ def get_queryset(self): class StoryList(ListView): - template_name = 'successstories/story_list.html' - context_object_name = 'stories' + template_name = "successstories/story_list.html" + context_object_name = "stories" def get_queryset(self): return Story.objects.select_related().latest() diff --git a/users/actions.py b/users/actions.py index 12313f5c5..b0d16f70d 100644 --- a/users/actions.py +++ b/users/actions.py @@ -4,26 +4,29 @@ def export_csv(modeladmin, request, queryset): - membership_name = { - 0: 'Basic', 1: 'Supporting', 2: 'Sponsor', 3: 'Managing', - 4: 'Contributing', 5: 'Fellow' - } - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename=membership.csv' + membership_name = {0: "Basic", 1: "Supporting", 2: "Sponsor", 3: "Managing", 4: "Contributing", 5: "Fellow"} + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = "attachment; filename=membership.csv" fieldnames = [ - 'membership_type', 'creator', 'email_address', 'votes', - 'last_vote_affirmation', + "membership_type", + "creator", + "email_address", + "votes", + "last_vote_affirmation", ] writer = csv.DictWriter(response, fieldnames=fieldnames) writer.writeheader() for obj in queryset: - writer.writerow({ - 'membership_type': membership_name.get(obj.membership_type), - 'creator': obj.creator, - 'email_address': obj.email_address, - 'votes': obj.votes, - 'last_vote_affirmation': obj.last_vote_affirmation, - }) + writer.writerow( + { + "membership_type": membership_name.get(obj.membership_type), + "creator": obj.creator, + "email_address": obj.email_address, + "votes": obj.votes, + "last_vote_affirmation": obj.last_vote_affirmation, + } + ) return response -export_csv.short_description = 'Export CSV' + +export_csv.short_description = "Export CSV" diff --git a/users/admin.py b/users/admin.py index 36d7e30f3..629c9086f 100644 --- a/users/admin.py +++ b/users/admin.py @@ -10,43 +10,51 @@ from .actions import export_csv from .models import User, Membership -TokenAdmin.search_fields = ('user__username',) -TokenAdmin.raw_id_fields = ('user',) +TokenAdmin.search_fields = ("user__username",) +TokenAdmin.raw_id_fields = ("user",) class MembershipInline(admin.StackedInline): model = Membership extra = 0 - readonly_fields = ('created', 'updated') + readonly_fields = ("created", "updated") class ApiKeyInline(TastypieApiKeyInline): - readonly_fields = ('key', 'created') + readonly_fields = ("key", "created") @admin.register(User) class UserAdmin(BaseUserAdmin): - inlines = BaseUserAdmin.inlines + (ApiKeyInline, MembershipInline,) + inlines = BaseUserAdmin.inlines + ( + ApiKeyInline, + MembershipInline, + ) fieldsets = ( - (None, {'fields': ('username', 'password')}), - (_('Personal info'), {'fields': ( - 'first_name', 'last_name', 'email', 'bio', - )}), - (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', - 'groups', 'user_permissions')}), - (_('Important dates'), {'fields': ('last_login', 'date_joined')}), + (None, {"fields": ("username", "password")}), + ( + _("Personal info"), + { + "fields": ( + "first_name", + "last_name", + "email", + "bio", + ) + }, + ), + (_("Permissions"), {"fields": ("is_active", "is_staff", "is_superuser", "groups", "user_permissions")}), + (_("Important dates"), {"fields": ("last_login", "date_joined")}), ) - list_display = ('username', 'email', 'full_name', 'is_staff', 'is_active') - list_editable = ('is_active',) - search_fields = BaseUserAdmin.search_fields + ('bio',) + list_display = ("username", "email", "full_name", "is_staff", "is_active") + list_editable = ("is_active",) + search_fields = BaseUserAdmin.search_fields + ("bio",) show_full_result_count = False def has_add_permission(self, request): return False - @admin.display( - description='Name' - ) + @admin.display(description="Name") def full_name(self, obj): return obj.get_full_name() @@ -54,12 +62,8 @@ def full_name(self, obj): @admin.register(Membership) class MembershipAdmin(admin.ModelAdmin): actions = [export_csv] - list_display = ( - '__str__', - 'created', - 'updated' - ) - date_hierarchy = 'created' - search_fields = ['creator__username'] - list_filter = ['membership_type'] - raw_id_fields = ['creator'] + list_display = ("__str__", "created", "updated") + date_hierarchy = "created" + search_fields = ["creator__username"] + list_filter = ["membership_type"] + raw_id_fields = ["creator"] diff --git a/users/apps.py b/users/apps.py index ed64f2093..ba7ffceae 100644 --- a/users/apps.py +++ b/users/apps.py @@ -2,9 +2,8 @@ class UsersAppConfig(AppConfig): - - name = 'users' - verbose_name = 'Users' + name = "users" + verbose_name = "Users" def ready(self): import users.listeners diff --git a/users/factories.py b/users/factories.py index 3ba8ddae7..ff8d97b3a 100644 --- a/users/factories.py +++ b/users/factories.py @@ -5,24 +5,27 @@ class UserFactory(DjangoModelFactory): - class Meta: model = User - django_get_or_create = ('username',) - - username = factory.Faker('user_name') - email = factory.Faker('free_email') - password = factory.PostGenerationMethodCall('set_password', 'password') - search_visibility = factory.Iterator([ - User.SEARCH_PUBLIC, - User.SEARCH_PRIVATE, - ]) - email_privacy = factory.Iterator([ - User.EMAIL_PUBLIC, - User.EMAIL_PRIVATE, - User.EMAIL_NEVER, - ]) - membership = factory.RelatedFactory('users.factories.MembershipFactory', 'creator') + django_get_or_create = ("username",) + + username = factory.Faker("user_name") + email = factory.Faker("free_email") + password = factory.PostGenerationMethodCall("set_password", "password") + search_visibility = factory.Iterator( + [ + User.SEARCH_PUBLIC, + User.SEARCH_PRIVATE, + ] + ) + email_privacy = factory.Iterator( + [ + User.EMAIL_PUBLIC, + User.EMAIL_PRIVATE, + User.EMAIL_NEVER, + ] + ) + membership = factory.RelatedFactory("users.factories.MembershipFactory", "creator") @factory.post_generation def groups(self, create, extracted, **kwargs): @@ -34,10 +37,9 @@ def groups(self, create, extracted, **kwargs): class MembershipFactory(DjangoModelFactory): - class Meta: model = Membership - django_get_or_create = ('creator',) + django_get_or_create = ("creator",) psf_code_of_conduct = True psf_announcements = True @@ -47,5 +49,5 @@ class Meta: def initial_data(): return { - 'users': UserFactory.create_batch(size=10), + "users": UserFactory.create_batch(size=10), } diff --git a/users/forms.py b/users/forms.py index 89045bab1..1013d38c9 100644 --- a/users/forms.py +++ b/users/forms.py @@ -5,86 +5,78 @@ class UserProfileForm(ModelForm): - class Meta: model = User fields = [ - 'username', - 'first_name', - 'last_name', - 'email', - 'bio', - 'search_visibility', - 'email_privacy', - 'public_profile', + "username", + "first_name", + "last_name", + "email", + "bio", + "search_visibility", + "email_privacy", + "public_profile", ] widgets = { - 'search_visibility': forms.RadioSelect, - 'email_privacy': forms.RadioSelect, + "search_visibility": forms.RadioSelect, + "email_privacy": forms.RadioSelect, } def clean_username(self): try: - user = User.objects.get_by_natural_key(self.cleaned_data.get('username')) + user = User.objects.get_by_natural_key(self.cleaned_data.get("username")) except User.MultipleObjectsReturned: - raise forms.ValidationError('A user with that username already exists.') + raise forms.ValidationError("A user with that username already exists.") except User.DoesNotExist: - return self.cleaned_data.get('username') + return self.cleaned_data.get("username") if user == self.instance: - return self.cleaned_data.get('username') - raise forms.ValidationError('A user with that username already exists.') + return self.cleaned_data.get("username") + raise forms.ValidationError("A user with that username already exists.") def clean_email(self): - email = self.cleaned_data.get('email') + email = self.cleaned_data.get("email") user = User.objects.filter(email=email).exclude(pk=self.instance.pk) if email is not None and user.exists(): - raise forms.ValidationError('Please use a unique email address.') + raise forms.ValidationError("Please use a unique email address.") return email class MembershipForm(ModelForm): - """ PSF Membership creation form """ - - COC_CHOICES = ( - ('', ''), - (True, 'Yes'), - (False, 'No') - ) - ACCOUNCEMENT_CHOICES = ( - (True, 'Yes'), - (False, 'No') - ) + """PSF Membership creation form""" + + COC_CHOICES = (("", ""), (True, "Yes"), (False, "No")) + ACCOUNCEMENT_CHOICES = ((True, "Yes"), (False, "No")) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['legal_name'].required = True - self.fields['preferred_name'].required = True - self.fields['city'].required = True - self.fields['region'].required = True - self.fields['country'].required = True - self.fields['postal_code'].required = True + self.fields["legal_name"].required = True + self.fields["preferred_name"].required = True + self.fields["city"].required = True + self.fields["region"].required = True + self.fields["country"].required = True + self.fields["postal_code"].required = True - code_of_conduct = self.fields['psf_code_of_conduct'] + code_of_conduct = self.fields["psf_code_of_conduct"] code_of_conduct.widget = forms.Select(choices=self.COC_CHOICES) class Meta: model = Membership fields = [ - 'legal_name', - 'preferred_name', - 'email_address', - 'city', - 'region', - 'country', - 'postal_code', - 'psf_code_of_conduct', + "legal_name", + "preferred_name", + "email_address", + "city", + "region", + "country", + "postal_code", + "psf_code_of_conduct", ] def clean_psf_code_of_conduct(self): - data = self.cleaned_data['psf_code_of_conduct'] + data = self.cleaned_data["psf_code_of_conduct"] if not data: - raise forms.ValidationError('Agreeing to the code of conduct is required.') + raise forms.ValidationError("Agreeing to the code of conduct is required.") return data @@ -99,4 +91,4 @@ class MembershipUpdateForm(MembershipForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - del(self.fields['psf_code_of_conduct']) + del self.fields["psf_code_of_conduct"] diff --git a/users/managers.py b/users/managers.py index 2f01dd550..d8465c349 100644 --- a/users/managers.py +++ b/users/managers.py @@ -3,7 +3,6 @@ class UserQuerySet(QuerySet): - def active(self): return self.filter(is_active=True) diff --git a/users/migrations/0001_initial.py b/users/migrations/0001_initial.py index 56e8f9a80..1789ee6be 100644 --- a/users/migrations/0001_initial.py +++ b/users/migrations/0001_initial.py @@ -6,60 +6,156 @@ class Migration(migrations.Migration): - dependencies = [ - ('auth', '0001_initial'), + ("auth", "0001_initial"), ] operations = [ migrations.CreateModel( - name='User', + name="User", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('password', models.CharField(verbose_name='password', max_length=128)), - ('last_login', models.DateTimeField(verbose_name='last login', default=django.utils.timezone.now)), - ('is_superuser', models.BooleanField(help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status', default=False)), - ('username', models.CharField(help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')], verbose_name='username', unique=True)), - ('first_name', models.CharField(blank=True, verbose_name='first name', max_length=30)), - ('last_name', models.CharField(blank=True, verbose_name='last name', max_length=30)), - ('email', models.EmailField(blank=True, verbose_name='email address', max_length=75)), - ('is_staff', models.BooleanField(help_text='Designates whether the user can log into this admin site.', verbose_name='staff status', default=False)), - ('is_active', models.BooleanField(help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active', default=True)), - ('date_joined', models.DateTimeField(verbose_name='date joined', default=django.utils.timezone.now)), - ('bio', markupfield.fields.MarkupField(blank=True, rendered_field=True)), - ('bio_markup_type', models.CharField(choices=[('', '--'), ('html', 'html'), ('plain', 'plain'), ('markdown', 'markdown'), ('restructuredtext', 'restructuredtext')], max_length=30, default='markdown', blank=True)), - ('search_visibility', models.IntegerField(choices=[(1, 'Allow search engines to index my profile page (recommended)'), (0, "Don't allow search engines to index my profile page")], default=1)), - ('_bio_rendered', models.TextField(editable=False)), - ('email_privacy', models.IntegerField(choices=[(0, 'Anyone can see my e-mail address'), (1, 'Only logged-in users can see my e-mail address'), (2, 'No one can ever see my e-mail address')], verbose_name='E-mail privacy', default=2)), - ('groups', models.ManyToManyField(help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.', related_name='user_set', blank=True, related_query_name='user', to='auth.Group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(help_text='Specific permissions for this user.', related_name='user_set', blank=True, related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)), + ("password", models.CharField(verbose_name="password", max_length=128)), + ("last_login", models.DateTimeField(verbose_name="last login", default=django.utils.timezone.now)), + ( + "is_superuser", + models.BooleanField( + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + default=False, + ), + ), + ( + "username", + models.CharField( + help_text="Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=30, + validators=[ + django.core.validators.RegexValidator("^[\\w.@+-]+$", "Enter a valid username.", "invalid") + ], + verbose_name="username", + unique=True, + ), + ), + ("first_name", models.CharField(blank=True, verbose_name="first name", max_length=30)), + ("last_name", models.CharField(blank=True, verbose_name="last name", max_length=30)), + ("email", models.EmailField(blank=True, verbose_name="email address", max_length=75)), + ( + "is_staff", + models.BooleanField( + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + default=False, + ), + ), + ( + "is_active", + models.BooleanField( + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + default=True, + ), + ), + ("date_joined", models.DateTimeField(verbose_name="date joined", default=django.utils.timezone.now)), + ("bio", markupfield.fields.MarkupField(blank=True, rendered_field=True)), + ( + "bio_markup_type", + models.CharField( + choices=[ + ("", "--"), + ("html", "html"), + ("plain", "plain"), + ("markdown", "markdown"), + ("restructuredtext", "restructuredtext"), + ], + max_length=30, + default="markdown", + blank=True, + ), + ), + ( + "search_visibility", + models.IntegerField( + choices=[ + (1, "Allow search engines to index my profile page (recommended)"), + (0, "Don't allow search engines to index my profile page"), + ], + default=1, + ), + ), + ("_bio_rendered", models.TextField(editable=False)), + ( + "email_privacy", + models.IntegerField( + choices=[ + (0, "Anyone can see my e-mail address"), + (1, "Only logged-in users can see my e-mail address"), + (2, "No one can ever see my e-mail address"), + ], + verbose_name="E-mail privacy", + default=2, + ), + ), + ( + "groups", + models.ManyToManyField( + help_text="The groups this user belongs to. A user will get all permissions granted to each of his/her group.", + related_name="user_set", + blank=True, + related_query_name="user", + to="auth.Group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + help_text="Specific permissions for this user.", + related_name="user_set", + blank=True, + related_query_name="user", + to="auth.Permission", + verbose_name="user permissions", + ), + ), ], options={ - 'verbose_name_plural': 'users', - 'verbose_name': 'user', - 'abstract': False, + "verbose_name_plural": "users", + "verbose_name": "user", + "abstract": False, }, bases=(models.Model,), ), migrations.CreateModel( - name='Membership', + name="Membership", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('legal_name', models.CharField(max_length=100)), - ('preferred_name', models.CharField(max_length=100)), - ('email_address', models.EmailField(max_length=100)), - ('city', models.CharField(blank=True, max_length=100)), - ('region', models.CharField(blank=True, verbose_name='State, Province or Region', max_length=100)), - ('country', models.CharField(blank=True, max_length=100)), - ('postal_code', models.CharField(blank=True, max_length=20)), - ('psf_code_of_conduct', models.NullBooleanField(verbose_name='I agree to the PSF Code of Conduct')), - ('psf_announcements', models.NullBooleanField(verbose_name='I would like to receive occasional PSF email announcements')), - ('created', models.DateTimeField(blank=True, default=django.utils.timezone.now)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('creator', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, blank=True, related_name='membership', on_delete=models.CASCADE)), + ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)), + ("legal_name", models.CharField(max_length=100)), + ("preferred_name", models.CharField(max_length=100)), + ("email_address", models.EmailField(max_length=100)), + ("city", models.CharField(blank=True, max_length=100)), + ("region", models.CharField(blank=True, verbose_name="State, Province or Region", max_length=100)), + ("country", models.CharField(blank=True, max_length=100)), + ("postal_code", models.CharField(blank=True, max_length=20)), + ("psf_code_of_conduct", models.NullBooleanField(verbose_name="I agree to the PSF Code of Conduct")), + ( + "psf_announcements", + models.NullBooleanField(verbose_name="I would like to receive occasional PSF email announcements"), + ), + ("created", models.DateTimeField(blank=True, default=django.utils.timezone.now)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ( + "creator", + models.ForeignKey( + null=True, + to=settings.AUTH_USER_MODEL, + blank=True, + related_name="membership", + on_delete=models.CASCADE, + ), + ), ], - options={ - }, + options={}, bases=(models.Model,), ), ] diff --git a/users/migrations/0002_auto_20150416_1853.py b/users/migrations/0002_auto_20150416_1853.py index 638f34a3e..73d268252 100644 --- a/users/migrations/0002_auto_20150416_1853.py +++ b/users/migrations/0002_auto_20150416_1853.py @@ -2,16 +2,26 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0001_initial'), + ("users", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='user', - name='bio_markup_type', - field=models.CharField(max_length=30, choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')], default='markdown', blank=True), + model_name="user", + name="bio_markup_type", + field=models.CharField( + max_length=30, + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + default="markdown", + blank=True, + ), preserve_default=True, ), ] diff --git a/users/migrations/0003_auto_20150503_2026.py b/users/migrations/0003_auto_20150503_2026.py index e57716ce9..7a9321063 100644 --- a/users/migrations/0003_auto_20150503_2026.py +++ b/users/migrations/0003_auto_20150503_2026.py @@ -2,21 +2,20 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0002_auto_20150416_1853'), + ("users", "0002_auto_20150416_1853"), ] operations = [ migrations.AddField( - model_name='membership', - name='last_vote_affirmation', + model_name="membership", + name="last_vote_affirmation", field=models.DateTimeField(blank=True, null=True), preserve_default=True, ), migrations.AddField( - model_name='membership', - name='votes', + model_name="membership", + name="votes", field=models.BooleanField(default=False), preserve_default=True, ), diff --git a/users/migrations/0004_auto_20150503_2100.py b/users/migrations/0004_auto_20150503_2100.py index 02f22cd9f..fd274e202 100644 --- a/users/migrations/0004_auto_20150503_2100.py +++ b/users/migrations/0004_auto_20150503_2100.py @@ -2,22 +2,31 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0003_auto_20150503_2026'), + ("users", "0003_auto_20150503_2026"), ] operations = [ migrations.AddField( - model_name='membership', - name='membership_type', - field=models.IntegerField(choices=[(0, 'Basic Member'), (1, 'Supporting Member'), (2, 'Sponsor Member'), (3, 'Managing Member'), (4, 'Contributing Member'), (5, 'Fellow')], default=0), + model_name="membership", + name="membership_type", + field=models.IntegerField( + choices=[ + (0, "Basic Member"), + (1, "Supporting Member"), + (2, "Sponsor Member"), + (3, "Managing Member"), + (4, "Contributing Member"), + (5, "Fellow"), + ], + default=0, + ), preserve_default=True, ), migrations.AlterField( - model_name='membership', - name='votes', - field=models.BooleanField(verbose_name='I would like to be a PSF Voting Member', default=False), + model_name="membership", + name="votes", + field=models.BooleanField(verbose_name="I would like to be a PSF Voting Member", default=False), preserve_default=True, ), ] diff --git a/users/migrations/0005_user_public_profile.py b/users/migrations/0005_user_public_profile.py index a79c0b151..836fde98d 100644 --- a/users/migrations/0005_user_public_profile.py +++ b/users/migrations/0005_user_public_profile.py @@ -2,16 +2,15 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0004_auto_20150503_2100'), + ("users", "0004_auto_20150503_2100"), ] operations = [ migrations.AddField( - model_name='user', - name='public_profile', - field=models.BooleanField(verbose_name='Make my profile public', default=True), + model_name="user", + name="public_profile", + field=models.BooleanField(verbose_name="Make my profile public", default=True), preserve_default=True, ), ] diff --git a/users/migrations/0006_auto_20150503_2124.py b/users/migrations/0006_auto_20150503_2124.py index c00098535..67fdcf059 100644 --- a/users/migrations/0006_auto_20150503_2124.py +++ b/users/migrations/0006_auto_20150503_2124.py @@ -3,16 +3,17 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0005_user_public_profile'), + ("users", "0005_user_public_profile"), ] operations = [ migrations.AlterField( - model_name='membership', - name='creator', - field=models.OneToOneField(null=True, blank=True, to=settings.AUTH_USER_MODEL, related_name='membership', on_delete=models.CASCADE), + model_name="membership", + name="creator", + field=models.OneToOneField( + null=True, blank=True, to=settings.AUTH_USER_MODEL, related_name="membership", on_delete=models.CASCADE + ), preserve_default=True, ), ] diff --git a/users/migrations/0007_auto_20150604_1555.py b/users/migrations/0007_auto_20150604_1555.py index d7df458b9..5d5ca4262 100644 --- a/users/migrations/0007_auto_20150604_1555.py +++ b/users/migrations/0007_auto_20150604_1555.py @@ -2,19 +2,18 @@ def create_psf_membership_flag(apps, schema_editor): - Flag = apps.get_model('waffle', 'Flag') + Flag = apps.get_model("waffle", "Flag") Flag.objects.create( - name='psf_membership', + name="psf_membership", testing=True, - note='This flag is used to show the PSF Basic and Advanced member registration process.' + note="This flag is used to show the PSF Basic and Advanced member registration process.", ) class Migration(migrations.Migration): - dependencies = [ - ('users', '0006_auto_20150503_2124'), - ('waffle', '0001_initial'), + ("users", "0006_auto_20150503_2124"), + ("waffle", "0001_initial"), ] operations = [ diff --git a/users/migrations/0008_auto_20170814_0301.py b/users/migrations/0008_auto_20170814_0301.py index 121092349..9c0080b2e 100644 --- a/users/migrations/0008_auto_20170814_0301.py +++ b/users/migrations/0008_auto_20170814_0301.py @@ -3,30 +3,49 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0007_auto_20150604_1555'), + ("users", "0007_auto_20150604_1555"), ] operations = [ migrations.AlterField( - model_name='user', - name='email', - field=models.EmailField(max_length=254, verbose_name='email address', blank=True), + model_name="user", + name="email", + field=models.EmailField(max_length=254, verbose_name="email address", blank=True), ), migrations.AlterField( - model_name='user', - name='groups', - field=models.ManyToManyField(verbose_name='groups', help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_query_name='user', blank=True, to='auth.Group', related_name='user_set'), + model_name="user", + name="groups", + field=models.ManyToManyField( + verbose_name="groups", + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_query_name="user", + blank=True, + to="auth.Group", + related_name="user_set", + ), ), migrations.AlterField( - model_name='user', - name='last_login', - field=models.DateTimeField(verbose_name='last login', null=True, blank=True), + model_name="user", + name="last_login", + field=models.DateTimeField(verbose_name="last login", null=True, blank=True), ), migrations.AlterField( - model_name='user', - name='username', - field=models.CharField(max_length=30, verbose_name='username', help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, error_messages={'unique': 'A user with that username already exists.'}, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')]), + model_name="user", + name="username", + field=models.CharField( + max_length=30, + verbose_name="username", + help_text="Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.", + unique=True, + error_messages={"unique": "A user with that username already exists."}, + validators=[ + django.core.validators.RegexValidator( + "^[\\w.@+-]+$", + "Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.", + "invalid", + ) + ], + ), ), ] diff --git a/users/migrations/0009_auto_20170821_2000.py b/users/migrations/0009_auto_20170821_2000.py index 99ce055eb..199d8f541 100644 --- a/users/migrations/0009_auto_20170821_2000.py +++ b/users/migrations/0009_auto_20170821_2000.py @@ -2,15 +2,24 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0008_auto_20170814_0301'), + ("users", "0008_auto_20170814_0301"), ] operations = [ migrations.AlterField( - model_name='user', - name='bio_markup_type', - field=models.CharField(choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')], default='markdown', max_length=30), + model_name="user", + name="bio_markup_type", + field=models.CharField( + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + default="markdown", + max_length=30, + ), ), ] diff --git a/users/migrations/0010_auto_20170828_1906.py b/users/migrations/0010_auto_20170828_1906.py index 2740d77b1..6403363fe 100644 --- a/users/migrations/0010_auto_20170828_1906.py +++ b/users/migrations/0010_auto_20170828_1906.py @@ -5,15 +5,26 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0009_auto_20170821_2000'), + ("users", "0009_auto_20170821_2000"), ] operations = [ migrations.AlterField( - model_name='user', - name='username', - field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username'), + model_name="user", + name="username", + field=models.CharField( + error_messages={"unique": "A user with that username already exists."}, + help_text="Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=30, + unique=True, + validators=[ + django.core.validators.RegexValidator( + "^[\\w.@+-]+$", + "Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.", + ) + ], + verbose_name="username", + ), ), ] diff --git a/users/migrations/0011_auto_20170902_0930.py b/users/migrations/0011_auto_20170902_0930.py index 5af80c0da..57f5904e8 100644 --- a/users/migrations/0011_auto_20170902_0930.py +++ b/users/migrations/0011_auto_20170902_0930.py @@ -5,15 +5,21 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0010_auto_20170828_1906'), + ("users", "0010_auto_20170828_1906"), ] operations = [ migrations.AlterField( - model_name='user', - name='username', - field=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'), + model_name="user", + name="username", + field=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", + ), ), ] diff --git a/users/migrations/0012_usergroup.py b/users/migrations/0012_usergroup.py index b9d7dbbff..25848cf15 100644 --- a/users/migrations/0012_usergroup.py +++ b/users/migrations/0012_usergroup.py @@ -4,23 +4,28 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0011_auto_20170902_0930'), + ("users", "0011_auto_20170902_0930"), ] operations = [ migrations.CreateModel( - name='UserGroup', + name="UserGroup", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255)), - ('location', models.CharField(max_length=255)), - ('url', models.URLField(verbose_name='URL')), - ('url_type', models.CharField(choices=[('meetup', 'meetup'), ('distribution list', 'distribution list'), ('other', 'other')], max_length=20)), - ('start_date', models.DateField(null=True)), - ('approved', models.BooleanField(default=False)), - ('trusted', models.BooleanField(default=False)), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=255)), + ("location", models.CharField(max_length=255)), + ("url", models.URLField(verbose_name="URL")), + ( + "url_type", + models.CharField( + choices=[("meetup", "meetup"), ("distribution list", "distribution list"), ("other", "other")], + max_length=20, + ), + ), + ("start_date", models.DateField(null=True)), + ("approved", models.BooleanField(default=False)), + ("trusted", models.BooleanField(default=False)), ], ), ] diff --git a/users/migrations/0013_auto_20180705_0348.py b/users/migrations/0013_auto_20180705_0348.py index 1e412dea4..8073ae0da 100644 --- a/users/migrations/0013_auto_20180705_0348.py +++ b/users/migrations/0013_auto_20180705_0348.py @@ -4,20 +4,22 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0012_usergroup'), + ("users", "0012_usergroup"), ] operations = [ migrations.AlterField( - model_name='user', - name='last_name', - field=models.CharField(blank=True, max_length=150, verbose_name='last name'), + model_name="user", + name="last_name", + field=models.CharField(blank=True, max_length=150, verbose_name="last name"), ), migrations.AlterField( - model_name='usergroup', - name='url_type', - field=models.CharField(choices=[('meetup', 'Meetup'), ('distribution list', 'Distribution List'), ('other', 'Other')], max_length=20), + model_name="usergroup", + name="url_type", + field=models.CharField( + choices=[("meetup", "Meetup"), ("distribution list", "Distribution List"), ("other", "Other")], + max_length=20, + ), ), ] diff --git a/users/migrations/0014_auto_20210801_2332.py b/users/migrations/0014_auto_20210801_2332.py index 8f248482a..966cdde97 100644 --- a/users/migrations/0014_auto_20210801_2332.py +++ b/users/migrations/0014_auto_20210801_2332.py @@ -4,20 +4,21 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0013_auto_20180705_0348'), + ("users", "0013_auto_20180705_0348"), ] operations = [ migrations.AlterField( - model_name='membership', - name='psf_announcements', - field=models.BooleanField(blank=True, null=True, verbose_name='I would like to receive occasional PSF email announcements'), + model_name="membership", + name="psf_announcements", + field=models.BooleanField( + blank=True, null=True, verbose_name="I would like to receive occasional PSF email announcements" + ), ), migrations.AlterField( - model_name='membership', - name='psf_code_of_conduct', - field=models.BooleanField(blank=True, null=True, verbose_name='I agree to the PSF Code of Conduct'), - ) + model_name="membership", + name="psf_code_of_conduct", + field=models.BooleanField(blank=True, null=True, verbose_name="I agree to the PSF Code of Conduct"), + ), ] diff --git a/users/migrations/0015_alter_user_first_name.py b/users/migrations/0015_alter_user_first_name.py index ac7715204..3e83296ac 100644 --- a/users/migrations/0015_alter_user_first_name.py +++ b/users/migrations/0015_alter_user_first_name.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0014_auto_20210801_2332'), + ("users", "0014_auto_20210801_2332"), ] operations = [ migrations.AlterField( - model_name='user', - name='first_name', - field=models.CharField(blank=True, max_length=150, verbose_name='first name'), + model_name="user", + name="first_name", + field=models.CharField(blank=True, max_length=150, verbose_name="first name"), ), ] diff --git a/users/models.py b/users/models.py index d80f5ceef..8753d1521 100644 --- a/users/models.py +++ b/users/models.py @@ -12,12 +12,12 @@ from .managers import UserManager -DEFAULT_MARKUP_TYPE = getattr(settings, 'DEFAULT_MARKUP_TYPE', 'markdown') +DEFAULT_MARKUP_TYPE = getattr(settings, "DEFAULT_MARKUP_TYPE", "markdown") class CustomUserManager(UserManager): def get_by_natural_key(self, username): - case_insensitive_username_field = '{}__iexact'.format(self.model.USERNAME_FIELD) + case_insensitive_username_field = "{}__iexact".format(self.model.USERNAME_FIELD) return self.get(**{case_insensitive_username_field: username}) @@ -27,7 +27,7 @@ class User(AbstractUser): SEARCH_PRIVATE = 0 SEARCH_PUBLIC = 1 SEARCH_CHOICES = ( - (SEARCH_PUBLIC, 'Allow search engines to index my profile page (recommended)'), + (SEARCH_PUBLIC, "Allow search engines to index my profile page (recommended)"), (SEARCH_PRIVATE, "Don't allow search engines to index my profile page"), ) search_visibility = models.IntegerField(choices=SEARCH_CHOICES, default=SEARCH_PUBLIC) @@ -36,18 +36,18 @@ class User(AbstractUser): EMAIL_PRIVATE = 1 EMAIL_NEVER = 2 EMAIL_CHOICES = ( - (EMAIL_PUBLIC, 'Anyone can see my e-mail address'), - (EMAIL_PRIVATE, 'Only logged-in users can see my e-mail address'), - (EMAIL_NEVER, 'No one can ever see my e-mail address'), + (EMAIL_PUBLIC, "Anyone can see my e-mail address"), + (EMAIL_PRIVATE, "Only logged-in users can see my e-mail address"), + (EMAIL_NEVER, "No one can ever see my e-mail address"), ) - email_privacy = models.IntegerField('E-mail privacy', choices=EMAIL_CHOICES, default=EMAIL_NEVER) + email_privacy = models.IntegerField("E-mail privacy", choices=EMAIL_CHOICES, default=EMAIL_NEVER) - public_profile = models.BooleanField('Make my profile public', default=True) + public_profile = models.BooleanField("Make my profile public", default=True) objects = CustomUserManager() def get_absolute_url(self): - return reverse('users:user_detail', kwargs={'slug': self.username}) + return reverse("users:user_detail", kwargs={"slug": self.username}) @property def has_membership(self): @@ -60,6 +60,7 @@ def has_membership(self): @property def sponsorships(self): from sponsors.models import Sponsorship + return Sponsorship.objects.visible_to(self) @property @@ -82,12 +83,12 @@ class Membership(models.Model): FELLOW = 5 MEMBERSHIP_CHOICES = ( - (BASIC, 'Basic Member'), - (SUPPORTING, 'Supporting Member'), - (SPONSOR, 'Sponsor Member'), - (MANAGING, 'Managing Member'), - (CONTRIBUTING, 'Contributing Member'), - (FELLOW, 'Fellow'), + (BASIC, "Basic Member"), + (SUPPORTING, "Supporting Member"), + (SPONSOR, "Sponsor Member"), + (MANAGING, "Managing Member"), + (CONTRIBUTING, "Contributing Member"), + (FELLOW, "Fellow"), ) membership_type = models.IntegerField(default=BASIC, choices=MEMBERSHIP_CHOICES) @@ -95,13 +96,15 @@ class Membership(models.Model): preferred_name = models.CharField(max_length=100) email_address = models.EmailField(max_length=100) city = models.CharField(max_length=100, blank=True) - region = models.CharField('State, Province or Region', max_length=100, blank=True) + region = models.CharField("State, Province or Region", max_length=100, blank=True) country = models.CharField(max_length=100, blank=True) postal_code = models.CharField(max_length=20, blank=True) # PSF fields - psf_code_of_conduct = models.BooleanField('I agree to the PSF Code of Conduct', blank=True, null=True) - psf_announcements = models.BooleanField('I would like to receive occasional PSF email announcements', blank=True, null=True) + psf_code_of_conduct = models.BooleanField("I agree to the PSF Code of Conduct", blank=True, null=True) + psf_announcements = models.BooleanField( + "I would like to receive occasional PSF email announcements", blank=True, null=True + ) # Voting votes = models.BooleanField("I would like to be a PSF Voting Member", default=False) @@ -112,7 +115,7 @@ class Membership(models.Model): creator = models.OneToOneField( User, - related_name='membership', + related_name="membership", null=True, blank=True, on_delete=models.CASCADE, @@ -156,16 +159,16 @@ def save(self, **kwargs): class UserGroup(models.Model): name = models.CharField(max_length=255) location = models.CharField(max_length=255) - url = models.URLField('URL') + url = models.URLField("URL") - TYPE_MEETUP = 'meetup' - TYPE_DISTRIBUTION_LIST = 'distribution list' - TYPE_OTHER = 'other' + TYPE_MEETUP = "meetup" + TYPE_DISTRIBUTION_LIST = "distribution list" + TYPE_OTHER = "other" TYPE_CHOICES = ( - (TYPE_MEETUP, 'Meetup'), - (TYPE_DISTRIBUTION_LIST, 'Distribution List'), - (TYPE_OTHER, 'Other'), + (TYPE_MEETUP, "Meetup"), + (TYPE_DISTRIBUTION_LIST, "Distribution List"), + (TYPE_OTHER, "Other"), ) url_type = models.CharField( max_length=20, diff --git a/users/templatetags/users_tags.py b/users/templatetags/users_tags.py index 820f8b4de..829971fc7 100644 --- a/users/templatetags/users_tags.py +++ b/users/templatetags/users_tags.py @@ -5,7 +5,7 @@ register = template.Library() -@register.filter(name='user_location') +@register.filter(name="user_location") def parse_location(user): """ Returns a formatted string of user location data. @@ -14,12 +14,12 @@ def parse_location(user): Returns empty if no location data is present """ - path = '' + path = "" try: membership = user.membership except Membership.DoesNotExist: - return '' + return "" if membership.city: path += "%s" % (membership.city) diff --git a/users/tests/test_forms.py b/users/tests/test_forms.py index 897f41d6c..e00cc0688 100644 --- a/users/tests/test_forms.py +++ b/users/tests/test_forms.py @@ -10,59 +10,53 @@ class UsersFormsTestCase(TestCase): - def test_signup_form(self): - form = SignupForm({ - 'username': 'username', - 'email': 'test@example.com', - 'password1': 'password', - 'password2': 'password' - }) + form = SignupForm( + {"username": "username", "email": "test@example.com", "password1": "password", "password2": "password"} + ) self.assertTrue(form.is_valid()) def test_password_mismatch(self): - form = SignupForm({ - 'username': 'username2', - 'email': 'test@example.com', - 'password1': 'password', - 'password2': 'passwordmismatch' - }) + form = SignupForm( + { + "username": "username2", + "email": "test@example.com", + "password1": "password", + "password2": "passwordmismatch", + } + ) self.assertFalse(form.is_valid()) # Since django-allauth 0.27.0, the "You must type the same password # each time" form validation error that can be triggered during # signup is added to the 'password2' field instead of being added to # the non field errors. - self.assertIn('password2', form.errors) - self.assertEqual( - form.errors['password2'], - ['You must type the same password each time.'] - ) + self.assertIn("password2", form.errors) + self.assertEqual(form.errors["password2"], ["You must type the same password each time."]) def test_duplicate_username(self): - User.objects.create_user('username2', 'test@example.com', 'testpass') - - form = SignupForm({ - 'username': 'username2', - 'email': 'test2@example.com', - 'password1': 'password', - 'password2': 'password' - }) + User.objects.create_user("username2", "test@example.com", "testpass") + + form = SignupForm( + {"username": "username2", "email": "test2@example.com", "password1": "password", "password2": "password"} + ) self.assertFalse(form.is_valid()) - self.assertIn('username', form.errors) + self.assertIn("username", form.errors) def test_duplicate_email(self): - user = User.objects.create_user('test1', 'test@example.com', 'testpass') + user = User.objects.create_user("test1", "test@example.com", "testpass") EmailAddress.objects.create(user=user, email="test@example.com") - form = SignupForm(data={ - 'username': 'username2', - 'email': 'test@example.com', - 'password1': 'password', - 'password2': 'password', - }) + form = SignupForm( + data={ + "username": "username2", + "email": "test@example.com", + "password1": "password", + "password2": "password", + } + ) self.assertFalse(form.is_valid()) - self.assertIn('email', form.errors) + self.assertIn("email", form.errors) def test_newline_in_username(self): # Note that since Django 1.9, forms.CharField().strip is True @@ -79,73 +73,75 @@ def test_newline_in_username(self): # # See #1045 and test_newline_in_username in # users/tests/test_views.py for details. - form = SignupForm({ - 'username': 'username\n', - 'email': 'test@example.com', - 'password1': 'password', - 'password2': 'password', - }) + form = SignupForm( + { + "username": "username\n", + "email": "test@example.com", + "password1": "password", + "password2": "password", + } + ) self.assertTrue(form.is_valid()) def test_non_ascii_username(self): - form = SignupForm({ - 'username': 'fööpython', - 'email': 'test@example.com', - 'password1': 'password', - 'password2': 'password', - }) + form = SignupForm( + { + "username": "fööpython", + "email": "test@example.com", + "password1": "password", + "password2": "password", + } + ) self.assertFalse(form.is_valid()) - expected_error = 'Enter a valid username. This value may contain only unaccented lowercase a-z and uppercase A-Z letters, numbers, and @/./+/-/_ characters.' - self.assertIn(expected_error, form.errors['username']) + expected_error = "Enter a valid username. This value may contain only unaccented lowercase a-z and uppercase A-Z letters, numbers, and @/./+/-/_ characters." + self.assertIn(expected_error, form.errors["username"]) def test_user_membership(self): - form = MembershipForm({ - 'legal_name': 'Some Name', - 'preferred_name': 'Sommy', - 'email_address': 'sommy@example.com', - 'city': 'Lawrence', - 'region': 'Kansas', - 'country': 'USA', - 'postal_code': '66044', - 'psf_announcements': True, - }) - self.assertFalse(form.is_valid()) - self.assertEqual( - form.errors['psf_code_of_conduct'], - ['Agreeing to the code of conduct is required.'] + form = MembershipForm( + { + "legal_name": "Some Name", + "preferred_name": "Sommy", + "email_address": "sommy@example.com", + "city": "Lawrence", + "region": "Kansas", + "country": "USA", + "postal_code": "66044", + "psf_announcements": True, + } ) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors["psf_code_of_conduct"], ["Agreeing to the code of conduct is required."]) class UserProfileFormTestCase(TestCase): - def test_unique_email(self): - User.objects.create_user('stanne', 'mikael@darktranquillity.com', 'testpass') - User.objects.create_user('test42', 'test42@example.com', 'testpass') - - form = UserProfileForm({ - 'username': 'stanne', - 'email': 'test42@example.com', - 'search_visibility': 0, - 'email_privacy': 0, - }, instance=User.objects.get(username='stanne')) - self.assertFalse(form.is_valid()) - self.assertEqual( - form.errors, - {'email': ['Please use a unique email address.']} + User.objects.create_user("stanne", "mikael@darktranquillity.com", "testpass") + User.objects.create_user("test42", "test42@example.com", "testpass") + + form = UserProfileForm( + { + "username": "stanne", + "email": "test42@example.com", + "search_visibility": 0, + "email_privacy": 0, + }, + instance=User.objects.get(username="stanne"), ) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors, {"email": ["Please use a unique email address."]}) def test_case_insensitive_unique_username(self): - User.objects.create_user('stanne', 'mikael@darktranquillity.com', 'testpass') - User.objects.create_user('test42', 'test42@example.com', 'testpass') - - form = UserProfileForm({ - 'username': 'Test42', - 'email': 'mikael@darktranquillity.com', - 'search_visibility': 0, - 'email_privacy': 0, - }, instance=User.objects.get(username='stanne')) - self.assertFalse(form.is_valid()) - self.assertEqual( - form.errors, - {'username': ['A user with that username already exists.']} + User.objects.create_user("stanne", "mikael@darktranquillity.com", "testpass") + User.objects.create_user("test42", "test42@example.com", "testpass") + + form = UserProfileForm( + { + "username": "Test42", + "email": "mikael@darktranquillity.com", + "search_visibility": 0, + "email_privacy": 0, + }, + instance=User.objects.get(username="stanne"), ) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors, {"username": ["A user with that username already exists."]}) diff --git a/users/tests/test_models.py b/users/tests/test_models.py index 5dda50111..2b2d842ac 100644 --- a/users/tests/test_models.py +++ b/users/tests/test_models.py @@ -12,19 +12,15 @@ class UsersModelsTestCase(TestCase): def test_create_superuser(self): - user = User.objects.create_superuser( - username='username', - password='password', - email='user@domain.com' - ) + user = User.objects.create_superuser(username="username", password="password", email="user@domain.com") self.assertNotEqual(user, None) self.assertTrue(user.is_active) self.assertTrue(user.is_superuser) self.assertTrue(user.is_staff) kwargs = { - 'username': '', - 'password': 'password', + "username": "", + "password": "password", } self.assertRaises(ValueError, User.objects.create_user, **kwargs) @@ -58,14 +54,14 @@ def test_needs_vote_affirmation(self): class UserGroupsModelsTestCase(TestCase): def test_create_usergroup(self): group = UserGroup.objects.create( - name='PLUG', - location='London, UK', - url='http://meetup.com/plug', + name="PLUG", + location="London, UK", + url="http://meetup.com/plug", url_type=UserGroup.TYPE_MEETUP, ) - self.assertEqual(group.name, 'PLUG') - self.assertEqual(group.location, 'London, UK') - self.assertEqual(group.url, 'http://meetup.com/plug') + self.assertEqual(group.name, "PLUG") + self.assertEqual(group.location, "London, UK") + self.assertEqual(group.url, "http://meetup.com/plug") self.assertEqual(group.url_type, UserGroup.TYPE_MEETUP) self.assertIsNone(group.start_date) self.assertFalse(group.approved) diff --git a/users/tests/test_templatetags.py b/users/tests/test_templatetags.py index f1c875c9c..0cc1c778b 100644 --- a/users/tests/test_templatetags.py +++ b/users/tests/test_templatetags.py @@ -4,42 +4,41 @@ class UsersTagsTest(TemplateTestCase): - def test_parse_location(self): user = UserFactory() template = "{% load users_tags %}{{ user|user_location }}" - rendered = self.render_string(template, {'user': user}) + rendered = self.render_string(template, {"user": user}) self.assertEqual(rendered, "") template = "{% load users_tags %}{{ user|user_location }}" - rendered = self.render_string(template, {'user': user}) + rendered = self.render_string(template, {"user": user}) self.assertEqual(rendered, "") template = "{% load users_tags %}{{ user|user_location|default:'Not Specified' }}" - user = UserFactory(membership__city='Lawrence') - rendered = self.render_string(template, {'user': user}) + user = UserFactory(membership__city="Lawrence") + rendered = self.render_string(template, {"user": user}) self.assertEqual(rendered, "Lawrence") - user = UserFactory(membership__city='Lawrence', membership__region='KS') - rendered = self.render_string(template, {'user': user}) + user = UserFactory(membership__city="Lawrence", membership__region="KS") + rendered = self.render_string(template, {"user": user}) self.assertEqual(rendered, "Lawrence, KS") - user = UserFactory(membership__region='KS', membership__country='USA') - rendered = self.render_string(template, {'user': user}) - self.assertEqual(rendered, 'KS USA') + user = UserFactory(membership__region="KS", membership__country="USA") + rendered = self.render_string(template, {"user": user}) + self.assertEqual(rendered, "KS USA") user = UserFactory( - membership__city='Lawrence', - membership__region='KS', - membership__country='US', + membership__city="Lawrence", + membership__region="KS", + membership__country="US", ) - rendered = self.render_string(template, {'user': user}) + rendered = self.render_string(template, {"user": user}) self.assertEqual(rendered, "Lawrence, KS US") - user = UserFactory(membership__city='Paris', membership__country='France') - rendered = self.render_string(template, {'user': user}) + user = UserFactory(membership__city="Paris", membership__country="France") + rendered = self.render_string(template, {"user": user}) self.assertEqual(rendered, "Paris, France") - user = UserFactory(membership__country='France') - rendered = self.render_string(template, {'user': user}) + user = UserFactory(membership__country="France") + rendered = self.render_string(template, {"user": user}) self.assertEqual(rendered, "France") diff --git a/users/tests/test_views.py b/users/tests/test_views.py index 83b8330f9..d56597780 100644 --- a/users/tests/test_views.py +++ b/users/tests/test_views.py @@ -17,108 +17,108 @@ class UsersViewsTestCase(TestCase): def setUp(self): self.user = UserFactory( - username='username', - password='password', - email='niklas@sundin.se', + username="username", + password="password", + email="niklas@sundin.se", search_visibility=User.SEARCH_PUBLIC, membership=None, ) self.user2 = UserFactory( - username='spameggs', - password='password', + username="spameggs", + password="password", search_visibility=User.SEARCH_PRIVATE, email_privacy=User.EMAIL_PRIVATE, public_profile=False, ) - def assertUserCreated(self, data=None, template_name='account/verification_sent.html'): + def assertUserCreated(self, data=None, template_name="account/verification_sent.html"): post_data = { - 'username': 'guido', - 'email': 'montyopython@python.org', - 'password1': 'password', - 'password2': 'password', + "username": "guido", + "email": "montyopython@python.org", + "password1": "password", + "password2": "password", settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, } post_data.update(data or {}) - url = reverse('account_signup') + url = reverse("account_signup") response = self.client.get(url) self.assertEqual(response.status_code, 200) response = self.client.post(url, post_data, follow=True) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, template_name) - user = User.objects.get(username=post_data['username']) - self.assertEqual(user.username, post_data['username']) - self.assertEqual(user.email, post_data['email']) + user = User.objects.get(username=post_data["username"]) + self.assertEqual(user.username, post_data["username"]) + self.assertEqual(user.email, post_data["email"]) return response def test_membership_create(self): - url = reverse('users:user_membership_create') + url = reverse("users:user_membership_create") response = self.client.get(url) self.assertEqual(response.status_code, 302) # Requires login now - self.client.login(username='username', password='password') + self.client.login(username="username", password="password") response = self.client.get(url) self.assertEqual(response.status_code, 200) post_data = { - 'legal_name': 'Some Name', - 'preferred_name': 'Sommy', - 'email_address': 'sommy@example.com', - 'city': 'Lawrence', - 'region': 'Kansas', - 'country': 'USA', - 'postal_code': '66044', - 'psf_code_of_conduct': True, - 'psf_announcements': True, + "legal_name": "Some Name", + "preferred_name": "Sommy", + "email_address": "sommy@example.com", + "city": "Lawrence", + "region": "Kansas", + "country": "USA", + "postal_code": "66044", + "psf_code_of_conduct": True, + "psf_announcements": True, settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, } response = self.client.post(url, post_data) self.assertEqual(response.status_code, 302) - self.assertRedirects(response, reverse('users:user_membership_thanks')) + self.assertRedirects(response, reverse("users:user_membership_thanks")) def test_membership_update(self): - url = reverse('users:user_membership_edit') + url = reverse("users:user_membership_edit") response = self.client.get(url) self.assertEqual(response.status_code, 302) # Requires login now self.assertTrue(self.user2.has_membership) - self.client.login(username=self.user2.username, password='password') + self.client.login(username=self.user2.username, password="password") response = self.client.get(url) self.assertEqual(response.status_code, 200) post_data = { - 'legal_name': 'Some Name', - 'preferred_name': 'Sommy', - 'email_address': 'sommy@example.com', - 'city': 'Lawrence', - 'region': 'Kansas', - 'country': 'USA', - 'postal_code': '66044', - 'psf_announcements': True, + "legal_name": "Some Name", + "preferred_name": "Sommy", + "email_address": "sommy@example.com", + "city": "Lawrence", + "region": "Kansas", + "country": "USA", + "postal_code": "66044", + "psf_announcements": True, settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, } response = self.client.post(url, post_data) self.assertEqual(response.status_code, 302) def test_membership_update_404(self): - url = reverse('users:user_membership_edit') + url = reverse("users:user_membership_edit") self.assertFalse(self.user.has_membership) - self.client.login(username=self.user.username, password='password') + self.client.login(username=self.user.username, password="password") response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_user_has_already_have_membership(self): # Should redirect to /membership/edit/ if user already # has membership. - url = reverse('users:user_membership_create') + url = reverse("users:user_membership_create") self.assertTrue(self.user2.has_membership) - self.client.login(username=self.user2.username, password='password') + self.client.login(username=self.user2.username, password="password") response = self.client.get(url) - self.assertRedirects(response, reverse('users:user_membership_edit')) + self.assertRedirects(response, reverse("users:user_membership_edit")) def test_user_update(self): - self.client.login(username='username', password='password') - url = reverse('users:user_profile_edit') + self.client.login(username="username", password="password") + url = reverse("users:user_profile_edit") response = self.client.get(url) self.assertEqual(response.status_code, 200) @@ -130,26 +130,26 @@ def test_user_update(self): def test_user_update_redirect(self): # see issue #925 - self.client.login(username='username', password='password') - url = reverse('users:user_profile_edit') + self.client.login(username="username", password="password") + url = reverse("users:user_profile_edit") response = self.client.get(url) self.assertEqual(response.status_code, 200) # should return 200 if the user does want to see their user profile post_data = { - 'username': 'username', - 'search_visibility': 0, - 'email_privacy': 1, - 'public_profile': False, - 'email': 'niklas@sundin.se', + "username": "username", + "search_visibility": 0, + "email_privacy": 1, + "public_profile": False, + "email": "niklas@sundin.se", settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, } response = self.client.post(url, post_data) - profile_url = reverse('users:user_detail', kwargs={'slug': 'username'}) + profile_url = reverse("users:user_detail", kwargs={"slug": "username"}) self.assertRedirects(response, profile_url) # should return 404 for another user - another_user_url = reverse('users:user_detail', kwargs={'slug': 'spameggs'}) + another_user_url = reverse("users:user_detail", kwargs={"slug": "spameggs"}) response = self.client.get(another_user_url) self.assertEqual(response.status_code, 404) @@ -161,27 +161,27 @@ def test_user_update_redirect(self): def test_user_detail(self): # Ensure detail page is viewable without login, but that edit URLs # do not appear - detail_url = reverse('users:user_detail', kwargs={'slug': self.user.username}) - edit_url = reverse('users:user_profile_edit') + detail_url = reverse("users:user_detail", kwargs={"slug": self.user.username}) + edit_url = reverse("users:user_profile_edit") response = self.client.get(detail_url) self.assertTrue(self.user.is_active) self.assertNotContains(response, edit_url) # Ensure edit url is available to logged in users - self.client.login(username='username', password='password') + self.client.login(username="username", password="password") response = self.client.get(detail_url) self.assertContains(response, edit_url) # Ensure inactive accounts shouldn't be shown to users. user = User.objects.create_user( - username='foobar', - password='baz', - email='paradiselost@example.com', + username="foobar", + password="baz", + email="paradiselost@example.com", ) user.is_active = False user.save() self.assertFalse(user.is_active) - detail_url = reverse('users:user_detail', kwargs={'slug': user.username}) + detail_url = reverse("users:user_detail", kwargs={"slug": user.username}) response = self.client.get(detail_url) self.assertEqual(response.status_code, 404) @@ -192,13 +192,13 @@ def test_special_usernames(self): # are allowed to view their profile pages since we allow them in # the username field u1 = User.objects.create_user( - username='user.name', - password='password', + username="user.name", + password="password", ) - detail_url = reverse('users:user_detail', kwargs={'slug': u1.username}) - edit_url = reverse('users:user_profile_edit') + detail_url = reverse("users:user_detail", kwargs={"slug": u1.username}) + edit_url = reverse("users:user_profile_edit") - self.client.login(username=u1.username, password='password') + self.client.login(username=u1.username, password="password") response = self.client.get(detail_url) self.assertEqual(response.status_code, 200) @@ -206,14 +206,14 @@ def test_special_usernames(self): self.assertEqual(response.status_code, 200) u2 = User.objects.create_user( - username='user@example.com', - password='password', + username="user@example.com", + password="password", ) - detail_url = reverse('users:user_detail', kwargs={'slug': u2.username}) - edit_url = reverse('users:user_profile_edit') + detail_url = reverse("users:user_detail", kwargs={"slug": u2.username}) + edit_url = reverse("users:user_profile_edit") - self.client.login(username=u2.username, password='password') + self.client.login(username=u2.username, password="password") response = self.client.get(detail_url) self.assertEqual(response.status_code, 200) @@ -221,53 +221,48 @@ def test_special_usernames(self): self.assertEqual(response.status_code, 200) def test_user_new_account(self): - self.assertUserCreated(data={ - 'username': 'thisusernamedoesntexist', - 'email': 'thereisnoemail@likesthis.com', - 'password1': 'password', - 'password2': 'password', - }) + self.assertUserCreated( + data={ + "username": "thisusernamedoesntexist", + "email": "thereisnoemail@likesthis.com", + "password1": "password", + "password2": "password", + } + ) def test_user_duplicate_username_email(self): post_data = { - 'username': 'thisusernamedoesntexist', - 'email': 'thereisnoemail@likesthis.com', - 'password1': 'password', - 'password2': 'password', + "username": "thisusernamedoesntexist", + "email": "thereisnoemail@likesthis.com", + "password1": "password", + "password2": "password", } self.assertUserCreated(data=post_data) - response = self.assertUserCreated( - data=post_data, template_name='account/signup.html' - ) - self.assertContains( - response, 'A user with that username already exists.' - ) - self.assertContains( - response, 'A user is already registered with this email address.' - ) + response = self.assertUserCreated(data=post_data, template_name="account/signup.html") + self.assertContains(response, "A user with that username already exists.") + self.assertContains(response, "A user is already registered with this email address.") def test_usernames(self): - url = reverse('account_signup') + url = reverse("account_signup") usernames = [ - 'foaso+bar', 'foo.barahgs', 'foo@barbazbaz', - 'foo.baarBAZ', + "foaso+bar", + "foo.barahgs", + "foo@barbazbaz", + "foo.baarBAZ", ] post_data = { - 'username': 'thisusernamedoesntexist', - 'email': 'thereisnoemail@likesthis.com', - 'password1': 'password', - 'password2': 'password', + "username": "thisusernamedoesntexist", + "email": "thereisnoemail@likesthis.com", + "password1": "password", + "password2": "password", settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, } for i, username in enumerate(usernames): with self.subTest(i=i, username=username): - post_data.update({ - 'username': username, - 'email': f'foo{i}@example.com' - }) + post_data.update({"username": username, "email": f"foo{i}@example.com"}) response = self.client.post(url, post_data, follow=True) self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'account/verification_sent.html') + self.assertTemplateUsed(response, "account/verification_sent.html") def test_is_active_login(self): # 'allauth.account.auth_backends.AuthenticationBackend' @@ -277,122 +272,106 @@ def test_is_active_login(self): # return True. The actual rejection performs by the # 'perform_login()' helper and it redirects inactive users # to a separate view. - url = reverse('account_login') + url = reverse("account_login") user = UserFactory(is_active=False) - data = {'login': user.username, 'password': 'password'} + data = {"login": user.username, "password": "password"} response = self.client.post(url, data) - self.assertRedirects(response, reverse('account_inactive')) - url = reverse('users:user_membership_create') + self.assertRedirects(response, reverse("account_inactive")) + url = reverse("users:user_membership_create") response = self.client.get(url) # Ensure that an inactive user didn't get logged in. - self.assertRedirects( - response, - '{}?next={}'.format(reverse('account_login'), url) - ) + self.assertRedirects(response, "{}?next={}".format(reverse("account_login"), url)) def test_user_delete_needs_to_be_logged_in(self): - url = reverse('users:user_delete', kwargs={'slug': self.user.username}) + url = reverse("users:user_delete", kwargs={"slug": self.user.username}) response = self.client.delete(url) - self.assertRedirects( - response, - '{}?next={}'.format(reverse('account_login'), url) - ) + self.assertRedirects(response, "{}?next={}".format(reverse("account_login"), url)) def test_user_delete_invalid_request_method(self): - url = reverse('users:user_delete', kwargs={'slug': self.user.username}) - self.client.login(username=self.user.username, password='password') + url = reverse("users:user_delete", kwargs={"slug": self.user.username}) + self.client.login(username=self.user.username, password="password") response = self.client.get(url) self.assertEqual(response.status_code, 405) def test_user_delete_different_user(self): - url = reverse('users:user_delete', kwargs={'slug': self.user.username}) - self.client.login(username=self.user2.username, password='password') + url = reverse("users:user_delete", kwargs={"slug": self.user.username}) + self.client.login(username=self.user2.username, password="password") response = self.client.delete(url) self.assertEqual(response.status_code, 403) def test_user_delete(self): - url = reverse('users:user_delete', kwargs={'slug': self.user.username}) - self.client.login(username=self.user.username, password='password') + url = reverse("users:user_delete", kwargs={"slug": self.user.username}) + self.client.login(username=self.user.username, password="password") response = self.client.delete(url) - self.assertRedirects(response, reverse('home')) + self.assertRedirects(response, reverse("home")) self.assertRaises(User.DoesNotExist, User.objects.get, username=self.user.username) self.assertRaises(Membership.DoesNotExist, Membership.objects.get, creator=self.user) def test_membership_delete_needs_to_be_logged_in(self): - url = reverse('users:user_membership_delete', kwargs={'slug': self.user2.username}) + url = reverse("users:user_membership_delete", kwargs={"slug": self.user2.username}) response = self.client.delete(url) - self.assertRedirects( - response, - '{}?next={}'.format(reverse('account_login'), url) - ) + self.assertRedirects(response, "{}?next={}".format(reverse("account_login"), url)) def test_membership_delete_invalid_request_method(self): - url = reverse('users:user_membership_delete', kwargs={'slug': self.user2.username}) - self.client.login(username=self.user2.username, password='password') + url = reverse("users:user_membership_delete", kwargs={"slug": self.user2.username}) + self.client.login(username=self.user2.username, password="password") response = self.client.get(url) self.assertEqual(response.status_code, 405) def test_membership_delete_different_user_membership(self): user = UserFactory() self.assertTrue(user.has_membership) - url = reverse('users:user_membership_delete', kwargs={'slug': user.username}) - self.client.login(username=self.user2.username, password='password') + url = reverse("users:user_membership_delete", kwargs={"slug": user.username}) + self.client.login(username=self.user2.username, password="password") response = self.client.delete(url) self.assertEqual(response.status_code, 403) def test_membership_does_not_exist(self): self.assertFalse(self.user.has_membership) - url = reverse('users:user_membership_delete', kwargs={'slug': self.user.username}) - self.client.login(username=self.user.username, password='password') + url = reverse("users:user_membership_delete", kwargs={"slug": self.user.username}) + self.client.login(username=self.user.username, password="password") response = self.client.delete(url) self.assertEqual(response.status_code, 404) def test_membership_delete(self): self.assertTrue(self.user2.has_membership) - url = reverse('users:user_membership_delete', kwargs={'slug': self.user2.username}) - self.client.login(username=self.user2.username, password='password') + url = reverse("users:user_membership_delete", kwargs={"slug": self.user2.username}) + self.client.login(username=self.user2.username, password="password") response = self.client.delete(url) - self.assertRedirects( - response, - reverse('users:user_detail', kwargs={'slug': self.user2.username}) - ) + self.assertRedirects(response, reverse("users:user_detail", kwargs={"slug": self.user2.username})) # TODO: We can't use 'self.user2.refresh_from_db()' because # of https://code.djangoproject.com/ticket/27846. with self.assertRaises(Membership.DoesNotExist): Membership.objects.get(pk=self.user2.membership.pk) def test_password_change_honeypot(self): - url = reverse('account_change_password') + url = reverse("account_change_password") data = { - 'oldpassword': 'password', - 'password1': 'newpassword', - 'password2': 'newpassword', + "oldpassword": "password", + "password1": "newpassword", + "password2": "newpassword", } - self.client.login(username=self.user.username, password='password') + self.client.login(username=self.user.username, password="password") response = self.client.post(url, data, follow=True) # We should get 400 without 'HONEYPOT_FIELD_NAME' # field in the post data. self.assertEqual(response.status_code, 400) data[settings.HONEYPOT_FIELD_NAME] = settings.HONEYPOT_VALUE response = self.client.post(url, data, follow=True) - self.assertRedirects(response, reverse('users:user_profile_edit')) + self.assertRedirects(response, reverse("users:user_profile_edit")) self.client.logout() - logged_in = self.client.login(username=self.user.username, - password='newpassword') + logged_in = self.client.login(username=self.user.username, password="newpassword") self.assertTrue(logged_in) class SponsorshipDetailViewTests(TestCase): - def setUp(self): self.user = baker.make(settings.AUTH_USER_MODEL) self.client.force_login(self.user) self.sponsorship = baker.make( Sponsorship, submited_by=self.user, status=Sponsorship.APPLIED, _fill_optional=True ) - self.url = reverse( - "users:sponsorship_application_detail", args=[self.sponsorship.pk] - ) + self.url = reverse("users:sponsorship_application_detail", args=[self.sponsorship.pk]) def test_display_template_with_sponsorship_info(self): response = self.client.get(self.url) @@ -421,7 +400,7 @@ def test_404_if_sponsorship_does_not_belong_to_user(self): self.assertEqual(response.status_code, 404) def test_list_assets(self): - cfg = baker.make(RequiredTextAssetConfiguration, internal_name='input') + cfg = baker.make(RequiredTextAssetConfiguration, internal_name="input") benefit = baker.make(SponsorBenefit, sponsorship=self.sponsorship) asset = cfg.create_benefit_feature(benefit) @@ -434,7 +413,7 @@ def test_list_assets(self): self.assertEqual(0, len(context["fulfilled_assets"])) def test_fulfilled_assets(self): - cfg = baker.make(RequiredTextAssetConfiguration, internal_name='input') + cfg = baker.make(RequiredTextAssetConfiguration, internal_name="input") benefit = baker.make(SponsorBenefit, sponsorship=self.sponsorship) asset = cfg.create_benefit_feature(benefit) asset.value = "information" @@ -450,7 +429,6 @@ def test_fulfilled_assets(self): class UpdateSponsorInfoViewTests(TestCase): - def setUp(self): self.user = baker.make(settings.AUTH_USER_MODEL) self.client.force_login(self.user) @@ -459,9 +437,7 @@ def setUp(self): ) self.sponsor = self.sponsorship.sponsor self.contact = baker.make("sponsors.SponsorContact", sponsor=self.sponsor) - self.url = reverse( - "users:edit_sponsor_info", args=[self.sponsor.pk] - ) + self.url = reverse("users:edit_sponsor_info", args=[self.sponsor.pk]) self.data = { "description": "desc", "name": "CompanyX", @@ -498,9 +474,7 @@ def test_404_if_sponsor_does_not_exist(self): def test_404_if_sponsor_from_sponsorship_from_another_user(self): sponsorship = baker.make(Sponsorship, _fill_optional=True) - self.url = reverse( - "users:edit_sponsor_info", args=[sponsorship.sponsor.pk] - ) + self.url = reverse("users:edit_sponsor_info", args=[sponsorship.sponsor.pk]) response = self.client.get(self.url) self.assertEqual(response.status_code, 404) @@ -523,7 +497,6 @@ def test_update_sponsor_and_contact(self): class UpdateSponsorshipAssetsViewTests(TestCase): - def setUp(self): self.user = baker.make(User) self.sponsorship = baker.make(Sponsorship, sponsor__name="foo", submited_by=self.user) diff --git a/users/urls.py b/users/urls.py index 3ca7eccb7..e0fbb738c 100644 --- a/users/urls.py +++ b/users/urls.py @@ -2,17 +2,21 @@ from django.urls import path, re_path -app_name = 'users' +app_name = "users" urlpatterns = [ - path('edit/', views.UserUpdate.as_view(), name='user_profile_edit'), - path('membership/', views.MembershipCreate.as_view(), name='user_membership_create'), - path('membership/edit/', views.MembershipUpdate.as_view(), name='user_membership_edit'), - re_path(r'^membership/delete/(?P<slug>[-a-zA-Z0-9_\@\.+]+)/$', views.MembershipDeleteView.as_view(), name='user_membership_delete'), - path('membership/thanks/', views.MembershipThanks.as_view(), name='user_membership_thanks'), - path('membership/affirm/', views.MembershipVoteAffirm.as_view(), name='membership_affirm_vote'), - path('membership/affirm/done/', views.MembershipVoteAffirmDone.as_view(), name='membership_affirm_vote_done'), - path('nominations/', views.UserNominationsView.as_view(), name='user_nominations_view'), - path('sponsorships/', views.UserSponsorshipsDashboard.as_view(), name='user_sponsorships_dashboard'), + path("edit/", views.UserUpdate.as_view(), name="user_profile_edit"), + path("membership/", views.MembershipCreate.as_view(), name="user_membership_create"), + path("membership/edit/", views.MembershipUpdate.as_view(), name="user_membership_edit"), + re_path( + r"^membership/delete/(?P<slug>[-a-zA-Z0-9_\@\.+]+)/$", + views.MembershipDeleteView.as_view(), + name="user_membership_delete", + ), + path("membership/thanks/", views.MembershipThanks.as_view(), name="user_membership_thanks"), + path("membership/affirm/", views.MembershipVoteAffirm.as_view(), name="membership_affirm_vote"), + path("membership/affirm/done/", views.MembershipVoteAffirmDone.as_view(), name="membership_affirm_vote_done"), + path("nominations/", views.UserNominationsView.as_view(), name="user_nominations_view"), + path("sponsorships/", views.UserSponsorshipsDashboard.as_view(), name="user_sponsorships_dashboard"), path( "sponsorships/sponsor/<int:pk>/", views.UpdateSponsorInfoView.as_view(), @@ -38,6 +42,6 @@ views.SponsorshipDetailView.as_view(), name="sponsorship_application_detail", ), - re_path(r'^(?P<slug>[-a-zA-Z0-9_\@\.+]+)/delete/$', views.UserDeleteView.as_view(), name='user_delete'), - re_path(r'^(?P<slug>[-a-zA-Z0-9_\@\.+]+)/$', views.UserDetail.as_view(), name='user_detail'), + re_path(r"^(?P<slug>[-a-zA-Z0-9_\@\.+]+)/delete/$", views.UserDeleteView.as_view(), name="user_delete"), + re_path(r"^(?P<slug>[-a-zA-Z0-9_\@\.+]+)/$", views.UserDetail.as_view(), name="user_detail"), ] diff --git a/users/views.py b/users/views.py index 23140853e..96c9c8bc2 100644 --- a/users/views.py +++ b/users/views.py @@ -12,9 +12,7 @@ from django.utils import timezone from django.utils.decorators import method_decorator from django.contrib.auth.decorators import login_required -from django.views.generic import ( - CreateView, DetailView, TemplateView, UpdateView, DeleteView, ListView, FormView -) +from django.views.generic import CreateView, DetailView, TemplateView, UpdateView, DeleteView, ListView, FormView from allauth.account.views import SignupView, PasswordChangeView from honeypot.decorators import check_honeypot @@ -24,7 +22,9 @@ from sponsors.models import Sponsor, BenefitFeature from .forms import ( - UserProfileForm, MembershipForm, MembershipUpdateForm, + UserProfileForm, + MembershipForm, + MembershipUpdateForm, ) from .models import Membership from sponsors.models import Sponsorship @@ -35,17 +35,17 @@ class MembershipCreate(LoginRequiredMixin, CreateView): model = Membership form_class = MembershipForm - template_name = 'users/membership_form.html' + template_name = "users/membership_form.html" @method_decorator(check_honeypot) def dispatch(self, *args, **kwargs): if self.request.user.is_authenticated and self.request.user.has_membership: - return redirect('users:user_membership_edit') + return redirect("users:user_membership_edit") return super().dispatch(*args, **kwargs) def get_form_kwargs(self): kwargs = super().get_form_kwargs() - kwargs['initial'] = {'email_address': self.request.user.email} + kwargs["initial"] = {"email_address": self.request.user.email} return kwargs def form_valid(self, form): @@ -56,8 +56,8 @@ def form_valid(self, form): # Send subscription email to mailing lists if settings.MAILING_LIST_PSF_MEMBERS and self.object.psf_announcements: send_mail( - subject='PSF Members Announce Signup from python.org', - message='subscribe', + subject="PSF Members Announce Signup from python.org", + message="subscribe", from_email=self.object.creator.email, recipient_list=[settings.MAILING_LIST_PSF_MEMBERS], ) @@ -65,12 +65,12 @@ def form_valid(self, form): return super().form_valid(form) def get_success_url(self): - return reverse('users:user_membership_thanks') + return reverse("users:user_membership_thanks") class MembershipUpdate(LoginRequiredMixin, UpdateView): form_class = MembershipUpdateForm - template_name = 'users/membership_form.html' + template_name = "users/membership_form.html" @method_decorator(check_honeypot) def dispatch(self, *args, **kwargs): @@ -89,32 +89,32 @@ def form_valid(self, form): return super().form_valid(form) def get_success_url(self): - return reverse('users:user_membership_thanks') + return reverse("users:user_membership_thanks") class MembershipThanks(TemplateView): - template_name = 'users/membership_thanks.html' + template_name = "users/membership_thanks.html" class MembershipVoteAffirm(TemplateView): - template_name = 'users/membership_vote_affirm.html' + template_name = "users/membership_vote_affirm.html" def post(self, request, *args, **kwargs): - """ Store the vote affirmation """ + """Store the vote affirmation""" self.request.user.membership.votes = True self.request.user.membership.last_vote_affirmation = timezone.now() self.request.user.membership.save() - return redirect('users:membership_affirm_vote_done') + return redirect("users:membership_affirm_vote_done") class MembershipVoteAffirmDone(TemplateView): - template_name = 'users/membership_vote_affirm_done.html' + template_name = "users/membership_vote_affirm_done.html" class UserUpdate(LoginRequiredMixin, UpdateView): form_class = UserProfileForm - slug_field = 'username' - template_name = 'users/user_form.html' + slug_field = "username" + template_name = "users/user_form.html" @method_decorator(check_honeypot) def dispatch(self, *args, **kwargs): @@ -125,17 +125,16 @@ def get_object(self, queryset=None): class UserDetail(DetailView): - slug_field = 'username' + slug_field = "username" def get_queryset(self): queryset = User.objects.select_related() - if self.request.user.username == self.kwargs['slug']: + if self.request.user.username == self.kwargs["slug"]: return queryset return queryset.searchable() class HoneypotSignupView(SignupView): - @method_decorator(check_honeypot) def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs) @@ -150,15 +149,15 @@ def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs) def get_success_url(self): - return reverse('users:user_profile_edit') + return reverse("users:user_profile_edit") class UserDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView): model = User - success_url = reverse_lazy('home') - slug_field = 'username' + success_url = reverse_lazy("home") + slug_field = "username" raise_exception = True - http_method_names = ['post', 'delete'] + http_method_names = ["post", "delete"] def test_func(self): return self.get_object() == self.request.user @@ -166,12 +165,12 @@ def test_func(self): class MembershipDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView): model = Membership - slug_field = 'creator__username' + slug_field = "creator__username" raise_exception = True - http_method_names = ['post', 'delete'] + http_method_names = ["post", "delete"] def get_success_url(self): - return reverse('users:user_detail', kwargs={'slug': self.request.user.username}) + return reverse("users:user_detail", kwargs={"slug": self.request.user.username}) def test_func(self): return self.get_object().creator == self.request.user @@ -179,30 +178,30 @@ def test_func(self): class UserNominationsView(LoginRequiredMixin, TemplateView): model = User - template_name = 'users/nominations_view.html' + template_name = "users/nominations_view.html" def get_queryset(self): return User.objects.select_related() def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - elections = defaultdict(lambda: {'nominations_recieved': [], 'nominations_made': []}) + elections = defaultdict(lambda: {"nominations_recieved": [], "nominations_made": []}) for nomination in self.request.user.nominations_recieved.all(): nominations = nomination.nominations.all() for nomin in nominations: nomin.is_editable = nomin.editable(user=self.request.user) - elections[nomination.election]['nominations_recieved'].append(nomin) + elections[nomination.election]["nominations_recieved"].append(nomin) for nomination in self.request.user.nominations_made.all(): nomination.is_editable = nomination.editable(user=self.request.user) - elections[nomination.election]['nominations_made'].append(nomination) - context['elections'] = dict(sorted(dict(elections).items(), key=lambda item: item[0].date, reverse=True)) + elections[nomination.election]["nominations_made"].append(nomination) + context["elections"] = dict(sorted(dict(elections).items(), key=lambda item: item[0].date, reverse=True)) return context @method_decorator(login_required(login_url=settings.LOGIN_URL), name="dispatch") class UserSponsorshipsDashboard(ListView): - context_object_name = 'sponsorships' - template_name = 'users/list_user_sponsorships.html' + context_object_name = "sponsorships" + template_name = "users/list_user_sponsorships.html" def get_queryset(self): return self.request.user.sponsorships.select_related("sponsor") @@ -215,12 +214,7 @@ def get_context_data(self, *args, **kwargs): by_status = [] inactive = [sp for sp in sponsorships if not sp.is_active] for value, label in Sponsorship.STATUS_CHOICES[::-1]: - by_status.append(( - label, [ - sp for sp in inactive - if sp.status == value - ] - )) + by_status.append((label, [sp for sp in inactive if sp.status == value])) context["by_status"] = by_status return context @@ -228,8 +222,8 @@ def get_context_data(self, *args, **kwargs): @method_decorator(login_required(login_url=settings.LOGIN_URL), name="dispatch") class SponsorshipDetailView(DetailView): - context_object_name = 'sponsorship' - template_name = 'users/sponsorship_detail.html' + context_object_name = "sponsorship" + template_name = "users/sponsorship_detail.html" def get_queryset(self): if self.request.user.is_superuser: @@ -264,7 +258,7 @@ def get_context_data(self, *args, **kwargs): @method_decorator(login_required(login_url=settings.LOGIN_URL), name="dispatch") class UpdateSponsorInfoView(UpdateView): object_name = "sponsor" - template_name = 'sponsors/new_sponsorship_application_form.html' + template_name = "sponsors/new_sponsorship_application_form.html" form_class = SponsorUpdateForm def get_queryset(self): @@ -277,23 +271,24 @@ def get_success_url(self): messages.add_message(self.request, messages.SUCCESS, "Sponsor info updated with success.") return self.request.path + @login_required(login_url=settings.LOGIN_URL) def edit_sponsor_info_implicit(request): sponsors = Sponsor.objects.filter(contacts__user=request.user).all() if len(sponsors) == 0: messages.add_message(request, messages.INFO, "No Sponsors associated with your user.") - return redirect('users:user_profile_edit') + return redirect("users:user_profile_edit") elif len(sponsors) == 1: - return redirect('users:edit_sponsor_info', pk=sponsors[0].id) + return redirect("users:edit_sponsor_info", pk=sponsors[0].id) else: messages.add_message(request, messages.INFO, "Multiple Sponsors associated with your user.") - return render(request, 'users/sponsor_select.html', context={"sponsors": sponsors}) + return render(request, "users/sponsor_select.html", context={"sponsors": sponsors}) @method_decorator(login_required(login_url=settings.LOGIN_URL), name="dispatch") class UpdateSponsorshipAssetsView(UpdateView): object_name = "sponsorship" - template_name = 'users/sponsorship_assets_update.html' + template_name = "users/sponsorship_assets_update.html" form_class = SponsorRequiredAssetsForm def get_queryset(self): @@ -325,7 +320,7 @@ def form_valid(self, form): @method_decorator(login_required(login_url=settings.LOGIN_URL), name="dispatch") class ProvidedSponsorshipAssetsView(DetailView): object_name = "sponsorship" - template_name = 'users/sponsorship_assets_view.html' + template_name = "users/sponsorship_assets_view.html" def get_queryset(self): if self.request.user.is_superuser: diff --git a/work_groups/admin.py b/work_groups/admin.py index e17e300b3..90c6d8ac0 100644 --- a/work_groups/admin.py +++ b/work_groups/admin.py @@ -7,30 +7,35 @@ @admin.register(WorkGroup) class WorkGroupAdmin(ContentManageableModelAdmin): - search_fields = ['name', 'slug', 'url', 'short_description', 'purpose'] - list_display = ('name', 'active', 'approved') - list_filter = ('active', 'approved') + search_fields = ["name", "slug", "url", "short_description", "purpose"] + list_display = ("name", "active", "approved") + list_filter = ("active", "approved") fieldsets = [ - (None, {'fields': ( - 'name', - 'slug', - 'active', - 'approved', - 'url', - 'short_description', - 'purpose', - 'purpose_markup_type', - 'active_time', - 'active_time_markup_type', - 'core_values', - 'core_values_markup_type', - 'rules', - 'rules_markup_type', - 'communication', - 'communication_markup_type', - 'support', - 'support_markup_type', - 'organizers', - 'members', - )}) + ( + None, + { + "fields": ( + "name", + "slug", + "active", + "approved", + "url", + "short_description", + "purpose", + "purpose_markup_type", + "active_time", + "active_time_markup_type", + "core_values", + "core_values_markup_type", + "rules", + "rules_markup_type", + "communication", + "communication_markup_type", + "support", + "support_markup_type", + "organizers", + "members", + ) + }, + ) ] diff --git a/work_groups/apps.py b/work_groups/apps.py index e2174c5ec..7bdc75ae8 100644 --- a/work_groups/apps.py +++ b/work_groups/apps.py @@ -2,5 +2,4 @@ class WorkGroupsAppConfig(AppConfig): - - name = 'work_groups' + name = "work_groups" diff --git a/work_groups/migrations/0001_initial.py b/work_groups/migrations/0001_initial.py index dbfd0fa8e..e70580fc1 100644 --- a/work_groups/migrations/0001_initial.py +++ b/work_groups/migrations/0001_initial.py @@ -5,49 +5,185 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='WorkGroup', + name="WorkGroup", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(default=django.utils.timezone.now, db_index=True, blank=True)), - ('updated', models.DateTimeField(default=django.utils.timezone.now, blank=True)), - ('name', models.CharField(max_length=200)), - ('slug', models.SlugField(unique=True)), - ('active', models.BooleanField(default=True, db_index=True)), - ('approved', models.BooleanField(default=False, db_index=True)), - ('short_description', models.TextField(help_text='Short description used on listing pages', blank=True)), - ('purpose', markupfield.fields.MarkupField(rendered_field=True, help_text='State what the mission of the group is. List all (if any) common goals that will be shared amongst the workgroup.')), - ('purpose_markup_type', models.CharField(default='restructuredtext', max_length=30, choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')])), - ('active_time', markupfield.fields.MarkupField(rendered_field=True, help_text='How long will this workgroup exist? If the mission is not complete by the stated time, is it extendable? Is so, for how long?')), - ('_purpose_rendered', models.TextField(editable=False)), - ('core_values', markupfield.fields.MarkupField(rendered_field=True, help_text='List the core values that the workgroup will adhere to throughout its existence. Will the workgroup adopt any statements? If so, which statement?')), - ('active_time_markup_type', models.CharField(default='restructuredtext', max_length=30, choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')])), - ('rules', markupfield.fields.MarkupField(rendered_field=True, help_text='Give a comprehensive explanation of how the decision making will work within the workgroup and list the rules that accompany these procedures.')), - ('core_values_markup_type', models.CharField(default='restructuredtext', max_length=30, choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')])), - ('_active_time_rendered', models.TextField(editable=False)), - ('rules_markup_type', models.CharField(default='restructuredtext', max_length=30, choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')])), - ('communication', markupfield.fields.MarkupField(rendered_field=True, help_text='How will the team communicate? How often will the team communicate?')), - ('_core_values_rendered', models.TextField(editable=False)), - ('_rules_rendered', models.TextField(editable=False)), - ('communication_markup_type', models.CharField(default='restructuredtext', max_length=30, choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')])), - ('support', markupfield.fields.MarkupField(rendered_field=True, help_text='What resources will you need from the PSF in order to have a functional and effective workgroup?', blank=True)), - ('_communication_rendered', models.TextField(editable=False)), - ('support_markup_type', models.CharField(default='restructuredtext', max_length=30, blank=True, choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')])), - ('url', models.URLField(help_text='Main URL for Group', blank=True)), - ('_support_rendered', models.TextField(editable=False)), - ('creator', models.ForeignKey(blank=True, related_name='work_groups_workgroup_creator', to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)), - ('last_modified_by', models.ForeignKey(blank=True, related_name='work_groups_workgroup_modified', to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)), - ('members', models.ManyToManyField(related_name='working_groups', to=settings.AUTH_USER_MODEL)), - ('organizers', models.ManyToManyField(related_name='+', to=settings.AUTH_USER_MODEL)), + ("id", models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name="ID")), + ("created", models.DateTimeField(default=django.utils.timezone.now, db_index=True, blank=True)), + ("updated", models.DateTimeField(default=django.utils.timezone.now, blank=True)), + ("name", models.CharField(max_length=200)), + ("slug", models.SlugField(unique=True)), + ("active", models.BooleanField(default=True, db_index=True)), + ("approved", models.BooleanField(default=False, db_index=True)), + ( + "short_description", + models.TextField(help_text="Short description used on listing pages", blank=True), + ), + ( + "purpose", + markupfield.fields.MarkupField( + rendered_field=True, + help_text="State what the mission of the group is. List all (if any) common goals that will be shared amongst the workgroup.", + ), + ), + ( + "purpose_markup_type", + models.CharField( + default="restructuredtext", + max_length=30, + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + ), + ), + ( + "active_time", + markupfield.fields.MarkupField( + rendered_field=True, + help_text="How long will this workgroup exist? If the mission is not complete by the stated time, is it extendable? Is so, for how long?", + ), + ), + ("_purpose_rendered", models.TextField(editable=False)), + ( + "core_values", + markupfield.fields.MarkupField( + rendered_field=True, + help_text="List the core values that the workgroup will adhere to throughout its existence. Will the workgroup adopt any statements? If so, which statement?", + ), + ), + ( + "active_time_markup_type", + models.CharField( + default="restructuredtext", + max_length=30, + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + ), + ), + ( + "rules", + markupfield.fields.MarkupField( + rendered_field=True, + help_text="Give a comprehensive explanation of how the decision making will work within the workgroup and list the rules that accompany these procedures.", + ), + ), + ( + "core_values_markup_type", + models.CharField( + default="restructuredtext", + max_length=30, + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + ), + ), + ("_active_time_rendered", models.TextField(editable=False)), + ( + "rules_markup_type", + models.CharField( + default="restructuredtext", + max_length=30, + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + ), + ), + ( + "communication", + markupfield.fields.MarkupField( + rendered_field=True, + help_text="How will the team communicate? How often will the team communicate?", + ), + ), + ("_core_values_rendered", models.TextField(editable=False)), + ("_rules_rendered", models.TextField(editable=False)), + ( + "communication_markup_type", + models.CharField( + default="restructuredtext", + max_length=30, + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + ), + ), + ( + "support", + markupfield.fields.MarkupField( + rendered_field=True, + help_text="What resources will you need from the PSF in order to have a functional and effective workgroup?", + blank=True, + ), + ), + ("_communication_rendered", models.TextField(editable=False)), + ( + "support_markup_type", + models.CharField( + default="restructuredtext", + max_length=30, + blank=True, + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + ), + ), + ("url", models.URLField(help_text="Main URL for Group", blank=True)), + ("_support_rendered", models.TextField(editable=False)), + ( + "creator", + models.ForeignKey( + blank=True, + related_name="work_groups_workgroup_creator", + to=settings.AUTH_USER_MODEL, + null=True, + on_delete=models.CASCADE, + ), + ), + ( + "last_modified_by", + models.ForeignKey( + blank=True, + related_name="work_groups_workgroup_modified", + to=settings.AUTH_USER_MODEL, + null=True, + on_delete=models.CASCADE, + ), + ), + ("members", models.ManyToManyField(related_name="working_groups", to=settings.AUTH_USER_MODEL)), + ("organizers", models.ManyToManyField(related_name="+", to=settings.AUTH_USER_MODEL)), ], options={ - 'abstract': False, + "abstract": False, }, bases=(models.Model,), ), diff --git a/work_groups/migrations/0002_auto_20150604_2203.py b/work_groups/migrations/0002_auto_20150604_2203.py index 1ece46619..5b5cdeefc 100644 --- a/work_groups/migrations/0002_auto_20150604_2203.py +++ b/work_groups/migrations/0002_auto_20150604_2203.py @@ -2,16 +2,15 @@ class Migration(migrations.Migration): - dependencies = [ - ('work_groups', '0001_initial'), + ("work_groups", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='workgroup', - name='url', - field=models.URLField(help_text='Main URL for Group', verbose_name='URL', blank=True), + model_name="workgroup", + name="url", + field=models.URLField(help_text="Main URL for Group", verbose_name="URL", blank=True), preserve_default=True, ), ] diff --git a/work_groups/migrations/0003_auto_20170821_2000.py b/work_groups/migrations/0003_auto_20170821_2000.py index 34d793ebd..c16477736 100644 --- a/work_groups/migrations/0003_auto_20170821_2000.py +++ b/work_groups/migrations/0003_auto_20170821_2000.py @@ -2,15 +2,24 @@ class Migration(migrations.Migration): - dependencies = [ - ('work_groups', '0002_auto_20150604_2203'), + ("work_groups", "0002_auto_20150604_2203"), ] operations = [ migrations.AlterField( - model_name='workgroup', - name='support_markup_type', - field=models.CharField(choices=[('', '--'), ('html', 'HTML'), ('plain', 'Plain'), ('markdown', 'Markdown'), ('restructuredtext', 'Restructured Text')], default='restructuredtext', max_length=30), + model_name="workgroup", + name="support_markup_type", + field=models.CharField( + choices=[ + ("", "--"), + ("html", "HTML"), + ("plain", "Plain"), + ("markdown", "Markdown"), + ("restructuredtext", "Restructured Text"), + ], + default="restructuredtext", + max_length=30, + ), ), ] diff --git a/work_groups/migrations/0004_auto_20180705_0352.py b/work_groups/migrations/0004_auto_20180705_0352.py index 631f85a95..f46990dcd 100644 --- a/work_groups/migrations/0004_auto_20180705_0352.py +++ b/work_groups/migrations/0004_auto_20180705_0352.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('work_groups', '0003_auto_20170821_2000'), + ("work_groups", "0003_auto_20170821_2000"), ] operations = [ migrations.AlterField( - model_name='workgroup', - name='slug', + model_name="workgroup", + name="slug", field=models.SlugField(max_length=200, unique=True), ), ] diff --git a/work_groups/migrations/0005_alter_workgroup_creator_and_more.py b/work_groups/migrations/0005_alter_workgroup_creator_and_more.py index a316aa482..209eecefc 100644 --- a/work_groups/migrations/0005_alter_workgroup_creator_and_more.py +++ b/work_groups/migrations/0005_alter_workgroup_creator_and_more.py @@ -6,21 +6,32 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('work_groups', '0004_auto_20180705_0352'), + ("work_groups", "0004_auto_20180705_0352"), ] operations = [ migrations.AlterField( - model_name='workgroup', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_creator', to=settings.AUTH_USER_MODEL), + model_name="workgroup", + name="creator", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_creator", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='workgroup', - name='last_modified_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_modified', to=settings.AUTH_USER_MODEL), + model_name="workgroup", + name="last_modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/work_groups/models.py b/work_groups/models.py index 85a2e0a6e..6fd383e98 100644 --- a/work_groups/models.py +++ b/work_groups/models.py @@ -5,13 +5,14 @@ from cms.models import ContentManageable, NameSlugModel -DEFAULT_MARKUP_TYPE = getattr(settings, 'DEFAULT_MARKUP_TYPE', 'restructuredtext') +DEFAULT_MARKUP_TYPE = getattr(settings, "DEFAULT_MARKUP_TYPE", "restructuredtext") class WorkGroup(ContentManageable, NameSlugModel): """ Model to store Python Working Groups """ + active = models.BooleanField(default=True, db_index=True) approved = models.BooleanField(default=False, db_index=True) @@ -46,12 +47,9 @@ class WorkGroup(ContentManageable, NameSlugModel): help_text="What resources will you need from the PSF in order to have a functional and effective workgroup?", ) - url = models.URLField('URL', blank=True, help_text="Main URL for Group") + url = models.URLField("URL", blank=True, help_text="Main URL for Group") - organizers = models.ManyToManyField( - settings.AUTH_USER_MODEL, - related_name="+" - ) + organizers = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="+") members = models.ManyToManyField( settings.AUTH_USER_MODEL,