From 5a666591219873ab7e5a49263ad4107f3addf354 Mon Sep 17 00:00:00 2001 From: Yakser Date: Sun, 30 Apr 2023 12:35:36 +0300 Subject: [PATCH 1/5] Update patch method for UserDetail view --- users/helpers.py | 17 +++++++++++++++++ users/services/__init__.py | 0 users/views.py | 19 +++++-------------- 3 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 users/services/__init__.py diff --git a/users/helpers.py b/users/helpers.py index 4ddc97a8..ac82043d 100644 --- a/users/helpers.py +++ b/users/helpers.py @@ -4,6 +4,7 @@ from core.utils import Email from users.constants import PROTOCOL +from users.models import UserAchievement def reset_email(user, request): @@ -62,3 +63,19 @@ def verify_email(user, request): } Email.send_email(data) + + +def update_achievements(achievements, pk): + # delete all old achievements + UserAchievement.objects.filter(user_id=pk).delete() + # create new achievements + UserAchievement.objects.bulk_create( + [ + UserAchievement( + user_id=pk, + title=achievement.get("title"), + status=achievement.get("status"), + ) + for achievement in achievements + ] + ) diff --git a/users/services/__init__.py b/users/services/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/users/views.py b/users/views.py index 4aa334d1..dc525f87 100644 --- a/users/views.py +++ b/users/views.py @@ -29,6 +29,7 @@ from users.helpers import ( reset_email, verify_email, + update_achievements, ) from users.constants import ( VERBOSE_ROLE_TYPES, @@ -115,24 +116,14 @@ class UserDetail(RetrieveUpdateDestroyAPIView): def put(self, request, pk): # bootleg version of updating achievements via user if request.data.get("achievements") is not None: - achievements = request.data.get("achievements") - # delete all old achievements - UserAchievement.objects.filter(user_id=pk).delete() - # create new achievements - UserAchievement.objects.bulk_create( - [ - UserAchievement( - user_id=pk, - title=achievement.get("title"), - status=achievement.get("status"), - ) - for achievement in achievements - ] - ) + update_achievements(request.data.get("achievements"), pk) return super().put(request, pk) @transaction.atomic def patch(self, request, pk): + # bootleg version of updating achievements via user + if request.data.get("achievements") is not None: + update_achievements(request.data.get("achievements"), pk) return super().patch(request, pk) From 56760558d0f8dbbe4ec25f3a4a0e0daa02d7b488 Mon Sep 17 00:00:00 2001 From: Yakser Date: Mon, 1 May 2023 13:47:07 +0300 Subject: [PATCH 2/5] Integration with ClickUp API Removed static folder --- .env.example | 5 +- procollab/settings.py | 4 +- static/admin/css/changelists_new.css | 325 -------------- static/admin/css/forms_new.css | 528 ----------------------- static/admin/css/login_new.css | 60 --- static/admin/css/z_base_site.css | 188 -------- templates/admin/app_list_nav_change.html | 40 -- templates/admin/base_site.html | 15 - templates/admin/change_form.html | 81 ---- templates/admin/change_list.html | 88 ---- templates/admin/index.html | 50 --- templates/admin/login.html | 68 --- users/models.py | 1 + users/services/verification.py | 65 +++ users/views.py | 19 +- 15 files changed, 87 insertions(+), 1450 deletions(-) delete mode 100644 static/admin/css/changelists_new.css delete mode 100644 static/admin/css/forms_new.css delete mode 100644 static/admin/css/login_new.css delete mode 100644 static/admin/css/z_base_site.css delete mode 100644 templates/admin/app_list_nav_change.html delete mode 100644 templates/admin/base_site.html delete mode 100644 templates/admin/change_form.html delete mode 100644 templates/admin/change_list.html delete mode 100644 templates/admin/index.html delete mode 100644 templates/admin/login.html create mode 100644 users/services/verification.py diff --git a/.env.example b/.env.example index 4e85c6da..13e8e71d 100644 --- a/.env.example +++ b/.env.example @@ -23,4 +23,7 @@ REDIS_HOST= AUTOPOSTING_ON= TELEGRAM_BOT_TOKEN= -TELEGRAM_CHANNEL= \ No newline at end of file +TELEGRAM_CHANNEL= + +CLICKUP_API_TOKEN= +CLICKUP_SPACE_ID= diff --git a/procollab/settings.py b/procollab/settings.py index 98e16ccb..374c4c2c 100644 --- a/procollab/settings.py +++ b/procollab/settings.py @@ -252,10 +252,8 @@ USE_TZ = True -# Static files (CSS, JavaScript, Images) - STATIC_URL = "/static/" -STATIC_ROOT = BASE_DIR / "staticfiles" +STATIC_ROOT = BASE_DIR / "static" STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" diff --git a/static/admin/css/changelists_new.css b/static/admin/css/changelists_new.css deleted file mode 100644 index 589010ff..00000000 --- a/static/admin/css/changelists_new.css +++ /dev/null @@ -1,325 +0,0 @@ -/* CHANGELISTS */ - -#changelist { - display: flex; - align-items: flex-start; - justify-content: space-between; -} - -#changelist .changelist-form-container { - flex: 1 1 auto; - min-width: 0; -} - -#changelist table { - width: 100%; -} - -.change-list .hiddenfields { display:none; } - -.change-list .filtered table { - border-right: none; -} - -.change-list .filtered { - min-height: 400px; -} - -.change-list .filtered .results, .change-list .filtered .paginator, -.filtered #toolbar, .filtered div.xfull { - width: auto; -} - -.change-list .filtered table tbody th { - padding-right: 1em; -} - -#changelist-form .results { - overflow-x: auto; - width: 100%; -} - -#changelist .toplinks { - border-bottom: 1px solid var(--hairline-color); -} - -#changelist .paginator { - color: var(--body-quiet-color); - border-bottom: 1px solid var(--hairline-color); - background: #00000000; - overflow: hidden; -} - -/* CHANGELIST TABLES */ - -#changelist table thead th { - padding: 0; - white-space: nowrap; - vertical-align: middle; -} - -#changelist table thead th.action-checkbox-column { - width: 1.5em; - text-align: center; -} - -#changelist table tbody td.action-checkbox { - text-align: center; -} - -#changelist table tfoot { - color: var(--body-quiet-color); -} - -/* TOOLBAR */ - -#toolbar { - padding: 8px 10px; - margin-bottom: 15px; - border-top: 1px solid var(--hairline-color); - border-bottom: 1px solid var(--hairline-color); - background: #00000000; - color: var(--body-quiet-color); -} - -#toolbar form input { - border-radius: 4px; - font-size: 0.875rem; - padding: 5px; - color: var(--body-fg); -} - -#toolbar #searchbar { - height: 19px; - border: 1px solid var(--border-color); - padding: 2px 5px; - margin: 0; - vertical-align: top; - font-size: 0.8125rem; - max-width: 100%; -} - -#toolbar #searchbar:focus { - border-color: var(--body-quiet-color); -} - -#toolbar form input[type="submit"] { - border: 1px solid var(--border-color); - font-size: 0.8125rem; - padding: 4px 8px; - margin: 0; - vertical-align: middle; - background: #00000000; - box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset; - cursor: pointer; - color: var(--body-fg); -} - -#toolbar form input[type="submit"]:focus, -#toolbar form input[type="submit"]:hover { - border-color: var(--body-quiet-color); -} - -#changelist-search img { - vertical-align: middle; - margin-right: 4px; -} - -#changelist-search .help { - word-break: break-word; -} - -/* FILTER COLUMN */ - -#changelist-filter { - flex: 0 0 240px; - order: 1; - background: var(--darkened-bg); - border-left: none; - margin: 0 0 0 30px; -} - -#changelist-filter h2 { - font-size: 0.875rem; - text-transform: uppercase; - letter-spacing: 0.5px; - padding: 5px 15px; - margin-bottom: 12px; - border-bottom: none; -} - -#changelist-filter h3, -#changelist-filter details summary { - font-weight: 400; - padding: 0 15px; - margin-bottom: 10px; -} - -#changelist-filter details summary > * { - display: inline; -} - -#changelist-filter details > summary { - list-style-type: none; -} - -#changelist-filter details > summary::-webkit-details-marker { - display: none; -} - -#changelist-filter details > summary::before { - content: '→'; - font-weight: bold; - color: var(--link-hover-color); -} - -#changelist-filter details[open] > summary::before { - content: '↓'; -} - -#changelist-filter ul { - margin: 5px 0; - padding: 0 15px 15px; - border-bottom: 1px solid var(--hairline-color); -} - -#changelist-filter ul:last-child { - border-bottom: none; -} - -#changelist-filter li { - list-style-type: none; - margin-left: 0; - padding-left: 0; -} - -#changelist-filter a { - display: block; - color: var(--body-quiet-color); - word-break: break-word; -} - -#changelist-filter li.selected { - border-left: 5px solid var(--hairline-color); - padding-left: 10px; - margin-left: -15px; -} - -#changelist-filter li.selected a { - color: var(--link-selected-fg); -} - -#changelist-filter a:focus, #changelist-filter a:hover, -#changelist-filter li.selected a:focus, -#changelist-filter li.selected a:hover { - color: var(--link-hover-color); -} - -#changelist-filter #changelist-filter-clear a { - font-size: 0.8125rem; - padding-bottom: 10px; - border-bottom: 1px solid var(--hairline-color); -} - -/* DATE DRILLDOWN */ - -.change-list ul.toplinks { - display: block; - float: left; - padding: 0; - margin: 0; - width: 100%; -} - -.change-list ul.toplinks li { - padding: 3px 6px; - font-weight: bold; - list-style-type: none; - display: inline-block; -} - -.change-list ul.toplinks .date-back a { - color: var(--body-quiet-color); -} - -.change-list ul.toplinks .date-back a:focus, -.change-list ul.toplinks .date-back a:hover { - color: var(--link-hover-color); -} - -/* ACTIONS */ - -.filtered .actions { - border-right: none; -} - -#changelist table input { - margin: 0; - vertical-align: baseline; -} - -#changelist table tbody tr.selected { - background-color: var(--selected-row); -} - -#changelist .actions { - padding: 10px; - background: #00000000; - border-top: none; - border-bottom: none; - line-height: 24px; - color: var(--body-quiet-color); - width: 100%; -} - -#changelist .actions span.all, -#changelist .actions span.action-counter, -#changelist .actions span.clear, -#changelist .actions span.question { - font-size: 0.8125rem; - margin: 0 0.5em; -} - -#changelist .actions:last-child { - border-bottom: none; -} - -#changelist .actions select { - vertical-align: top; - height: 24px; - color: var(--body-fg); - border: 1px solid var(--border-color); - border-radius: 4px; - font-size: 0.875rem; - padding: 0 0 0 4px; - margin: 0; - margin-left: 10px; -} - -#changelist .actions select:focus { - border-color: #00000000; -} - -#changelist .actions label { - display: inline-block; - vertical-align: middle; - font-size: 0.8125rem; -} - -#changelist .actions .button { - font-size: 0.8125rem; - border: 1px solid var(--border-color); - border-radius: 4px; - background: #00000000; - box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset; - cursor: pointer; - height: 24px; - line-height: 1; - padding: 4px 8px; - margin: 0; - color: var(--body-fg); -} - -#changelist .actions .button:focus, #changelist .actions .button:hover { - border-color: var(--body-quiet-color); -} diff --git a/static/admin/css/forms_new.css b/static/admin/css/forms_new.css deleted file mode 100644 index 12c2f8de..00000000 --- a/static/admin/css/forms_new.css +++ /dev/null @@ -1,528 +0,0 @@ -@import url('widgets.css'); - -/* FORM ROWS */ - -.form-row { - overflow: hidden; - padding: 10px; - font-size: 0.8125rem; - border-bottom: 1px solid #ffffff29; -} - -.form-row img, .form-row input { - vertical-align: middle; -} - -.form-row label input[type="checkbox"] { - margin-top: 0; - vertical-align: 0; -} - -form .form-row p { - padding-left: 0; -} - -/* FORM LABELS */ - -label { - font-weight: normal; - color: var(--body-quiet-color); - font-size: 0.8125rem; -} - -.required label, label.required { - font-weight: bold; - color: var(--body-fg); -} - -/* RADIO BUTTONS */ - -form div.radiolist div { - padding-right: 7px; -} - -form div.radiolist.inline div { - display: inline-block; -} - -form div.radiolist label { - width: auto; -} - -form div.radiolist input[type="radio"] { - margin: -2px 4px 0 0; - padding: 0; -} - -form ul.inline { - margin-left: 0; - padding: 0; -} - -form ul.inline li { - float: left; - padding-right: 7px; -} - -/* ALIGNED FIELDSETS */ - -.aligned label { - display: block; - padding: 4px 10px 0 0; - float: left; - width: 160px; - word-wrap: break-word; - line-height: 1; -} - -.aligned label:not(.vCheckboxLabel):after { - content: ''; - display: inline-block; - vertical-align: middle; - height: 26px; -} - -.aligned label + p, .aligned label + div.help, .aligned label + div.readonly { - padding: 6px 0; - margin-top: 0; - margin-bottom: 0; - margin-left: 170px; - overflow-wrap: break-word; -} - -.aligned ul label { - display: inline; - float: none; - width: auto; -} - -.aligned .form-row input { - margin-bottom: 0; -} - -.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField { - width: 350px; -} - -form .aligned ul { - margin-left: 160px; - padding-left: 10px; -} - -form .aligned div.radiolist { - display: inline-block; - margin: 0; - padding: 0; -} - -form .aligned p.help, -form .aligned div.help { - clear: left; - margin-top: 0; - margin-left: 160px; - padding-left: 10px; -} - -form .aligned label + p.help, -form .aligned label + div.help { - margin-left: 0; - padding-left: 0; -} - -form .aligned p.help:last-child, -form .aligned div.help:last-child { - margin-bottom: 0; - padding-bottom: 0; -} - -form .aligned input + p.help, -form .aligned textarea + p.help, -form .aligned select + p.help, -form .aligned input + div.help, -form .aligned textarea + div.help, -form .aligned select + div.help { - margin-left: 160px; - padding-left: 10px; -} - -form .aligned ul li { - list-style: none; -} - -form .aligned table p { - margin-left: 0; - padding-left: 0; -} - -.aligned .vCheckboxLabel { - float: none; - width: auto; - display: inline-block; - vertical-align: -3px; - padding: 0 0 5px 5px; -} - -.aligned .vCheckboxLabel + p.help, -.aligned .vCheckboxLabel + div.help { - margin-top: -4px; -} - -.colM .aligned .vLargeTextField, .colM .aligned .vXMLLargeTextField { - width: 610px; -} - -.checkbox-row p.help, -.checkbox-row div.help { - margin-left: 0; - padding-left: 0; -} - -fieldset .fieldBox { - float: left; - margin-right: 20px; -} - -/* WIDE FIELDSETS */ - -.wide label { - width: 200px; -} - -form .wide p, -form .wide input + p.help, -form .wide input + div.help { - margin-left: 200px; -} - -form .wide p.help, -form .wide div.help { - padding-left: 38px; -} - -form div.help ul { - padding-left: 0; - margin-left: 0; -} - -.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField { - width: 450px; -} - -/* COLLAPSED FIELDSETS */ - -fieldset.collapsed * { - display: none; -} - -fieldset.collapsed h2, fieldset.collapsed { - display: block; -} - -fieldset.collapsed { - border: 1px solid var(--hairline-color); - border-radius: 4px; - overflow: hidden; -} - -fieldset.collapsed h2 { - background: var(--darkened-bg); - color: var(--body-quiet-color); -} - -fieldset .collapse-toggle { - color: var(--header-link-color); -} - -fieldset.collapsed .collapse-toggle { - background: transparent; - display: inline; - color: var(--link-fg); -} - -/* MONOSPACE TEXTAREAS */ - -fieldset.monospace textarea { - font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; -} - -/* SUBMIT ROW */ - -.submit-row { - padding: 12px 14px 7px; - margin: 0 0 20px; - background: var(--darkened-bg); - border: 1px solid var(--hairline-color); - border-radius: 4px; - text-align: right; - overflow: hidden; -} - -body.popup .submit-row { - overflow: auto; -} - -.submit-row input { - height: 35px; - line-height: 15px; - margin: 0 0 5px 5px; -} - -.submit-row input.default { - margin: 0 0 5px 8px; - text-transform: uppercase; -} - -.submit-row p { - margin: 0.3em; -} - -.submit-row p.deletelink-box { - float: left; - margin: 0; -} - -.submit-row a.deletelink { - display: block; - background: var(--delete-button-bg); - border-radius: 4px; - padding: 10px 15px; - height: 15px; - line-height: 15px; - margin-bottom: 5px; - color: var(--button-fg); -} - -.submit-row a.closelink { - display: inline-block; - background: var(--close-button-bg); - border-radius: 4px; - padding: 10px 15px; - height: 15px; - line-height: 15px; - margin: 0 0 0 5px; - color: var(--button-fg); -} - -.submit-row a.deletelink:focus, -.submit-row a.deletelink:hover, -.submit-row a.deletelink:active { - background: var(--delete-button-hover-bg); -} - -.submit-row a.closelink:focus, -.submit-row a.closelink:hover, -.submit-row a.closelink:active { - background: var(--close-button-hover-bg); -} - -/* CUSTOM FORM FIELDS */ - -.vSelectMultipleField { - vertical-align: top; -} - -.vCheckboxField { - border: none; -} - -.vDateField, .vTimeField { - margin-right: 2px; - margin-bottom: 4px; -} - -.vDateField { - min-width: 6.85em; -} - -.vTimeField { - min-width: 4.7em; -} - -.vURLField { - width: 30em; -} - -.vLargeTextField, .vXMLLargeTextField { - width: 48em; -} - -.flatpages-flatpage #id_content { - height: 40.2em; -} - -.module table .vPositiveSmallIntegerField { - width: 2.2em; -} - -.vIntegerField { - width: 5em; -} - -.vBigIntegerField { - width: 10em; -} - -.vForeignKeyRawIdAdminField { - width: 5em; -} - -.vTextField, .vUUIDField { - width: 20em; -} - -/* INLINES */ - -.inline-group { - padding: 0; - margin: 0 0 30px; -} - -.inline-group thead th { - padding: 8px 10px; -} - -.inline-group .aligned label { - width: 160px; -} - -.inline-related { - position: relative; -} - -.inline-related h3 { - margin: 0; - color: var(--body-quiet-color); - padding: 5px; - font-size: 0.8125rem; - background: var(--darkened-bg); - border-top: 1px solid var(--hairline-color); - border-bottom: 1px solid var(--hairline-color); -} - -.inline-related h3 span.delete { - float: right; -} - -.inline-related h3 span.delete label { - margin-left: 2px; - font-size: 0.6875rem; -} - -.inline-related fieldset { - margin: 0; - background: #00000000; - border: none; - width: 100%; -} - -.inline-related fieldset.module h3 { - margin: 0; - padding: 2px 5px 3px 5px; - font-size: 0.6875rem; - text-align: left; - font-weight: bold; - background: #bcd; - color: var(--body-bg); -} - -.inline-group .tabular fieldset.module { - border: none; -} - -.inline-related.tabular fieldset.module table { - width: 100%; - overflow-x: scroll; -} - -.last-related fieldset { - border: none; -} - -.inline-group .tabular tr.has_original td { - padding-top: 2em; -} - -.inline-group .tabular tr td.original { - padding: 2px 0 0 0; - width: 0; - _position: relative; -} - -.inline-group .tabular th.original { - width: 0px; - padding: 0; -} - -.inline-group .tabular td.original p { - position: absolute; - left: 0; - height: 1.1em; - padding: 2px 9px; - overflow: hidden; - font-size: 0.5625rem; - font-weight: bold; - color: var(--body-quiet-color); - _width: 700px; -} - -.inline-group ul.tools { - padding: 0; - margin: 0; - list-style: none; -} - -.inline-group ul.tools li { - display: inline; - padding: 0 5px; -} - -.inline-group div.add-row, -.inline-group .tabular tr.add-row td { - color: var(--body-quiet-color); - background: var(--darkened-bg); - padding: 8px 10px; - border-bottom: 1px solid var(--hairline-color); -} - -.inline-group .tabular tr.add-row td { - padding: 8px 10px; - border-bottom: 1px solid var(--hairline-color); -} - -.inline-group ul.tools a.add, -.inline-group div.add-row a, -.inline-group .tabular tr.add-row td a { - background: url(../img/icon-addlink.svg) 0 1px no-repeat; - padding-left: 16px; - font-size: 0.75rem; -} - -.empty-form { - display: none; -} - -/* RELATED FIELD ADD ONE / LOOKUP */ - -.related-lookup { - margin-left: 5px; - display: inline-block; - vertical-align: middle; - background-repeat: no-repeat; - background-size: 14px; -} - -.related-lookup { - width: 16px; - height: 16px; - background-image: url(../img/search.svg); -} - -form .related-widget-wrapper ul { - display: inline-block; - margin-left: 0; - padding-left: 0; -} - -.clearable-file-input input { - margin-top: 0; -} diff --git a/static/admin/css/login_new.css b/static/admin/css/login_new.css deleted file mode 100644 index 17356b59..00000000 --- a/static/admin/css/login_new.css +++ /dev/null @@ -1,60 +0,0 @@ -/* LOGIN FORM */ - -.login { - height: auto; -} - -.login #header { - height: auto; - padding: 15px 16px; - justify-content: center; -} - -.login #header h1 { - font-size: 1.125rem; - margin: 0; -} - -.login #header h1 a { - color: var(--header-link-color); -} - -.login #content { - padding: 20px 20px 0; -} - -.login #container { - background: var(--body-bg); - border: 1px solid var(--hairline-color); - border-radius: 4px; - overflow: hidden; - width: 28em; - min-width: 300px; - margin: 100px auto; - height: auto; -} - -.login .form-row { - padding: 4px 0; -} - -.login .form-row label { - display: block; - line-height: 2em; -} - -.login .form-row #id_username, .login .form-row #id_password { - padding: 8px; - width: 100%; - box-sizing: border-box; -} - -.login .submit-row { - padding: 1em 0 0 0; - margin: 0; - text-align: center; -} - -.login .password-reset-link { - text-align: center; -} diff --git a/static/admin/css/z_base_site.css b/static/admin/css/z_base_site.css deleted file mode 100644 index f6be8cd9..00000000 --- a/static/admin/css/z_base_site.css +++ /dev/null @@ -1,188 +0,0 @@ -:root { - --primary: #7b4ae5a8; - --secondary: #7b4ae5a8; - --accent: #ffffff; - --primary-fg: #ffffffb5; - --body-fg: #ffffffb5; - --body-bg: #000000d6; - --body-quiet-color: #ffffffb5; - --body-loud-color: #000; - --header-color: #fff; - --header-branding-color: var(--accent); - --header-bg: var(--secondary); - --header-link-color: var(--primary-fg); - --breadcrumbs-fg: #ffffff; - --breadcrumbs-link-fg: var(--body-bg); - --breadcrumbs-bg: #b39be8; - --link-fg: #b7c0ffcc; - --link-hover-color: #7548ff; - --link-selected-fg: #5b80b2; - --hairline-color: #00000000; - --border-color: #606060; - --error-fg: #ba2121; - --message-success-bg: #025a00; - --message-warning-bg: #ff740033; - --message-error-bg: #ffefef; - --darkened-bg: #00000000; - --selected-bg: #31295d; - --selected-row: #31295d; - --button-fg: #fff; - --button-bg: var(--primary); - --button-hover-bg: #609ab6; - --default-button-bg: var(--secondary); - --default-button-hover-bg: #205067; - --close-button-bg: #888; - --close-button-hover-bg: #747474; - --delete-button-bg: #c2252563; - --delete-button-hover-bg: #ff0000c2; - --object-tools-fg: var(--button-fg); - --object-tools-bg: var(--close-button-bg); - --object-tools-hover-bg: var(--close-button-hover-bg); -} -#content-related .module{ - background: #00000036; -} - -input[type=text], input[type=password], input[type=email], input[type=url], -input[type=number], input[type=tel], textarea, .vTextField { - border: 1px solid #ccc; - border-radius: 4px; - padding: 5px 6px; - margin-top: 0; - color: #ffffff; - background-color: #00000000; -} - - -.module { - border: none; - margin-bottom: 30px; - background: #00000000; -} - -.module h2, .module caption, .inline-group h2 { - margin: 0; - padding: 10px 12px; - font-weight: 400; - font-size: 0.8125rem; - text-align: left; - background: var(--primary); - color: var(--header-link-color); -} - -.navbar_models_name { - padding: 10px 12px; - border: 1px solid #5b5b5b00; - border-radius: 4px; -} - - -.navbar_models { - font-size: 0.875rem; - font-weight: 700; - display: block; - padding: 10px 12px; - margin: 0 0 10px 0; - color: #ff0000; - border: 1px solid #555555; - border-radius: 4px; - background-color: #00ffbc00; - background-position: 5px 12px; - overflow-wrap: break-word; -} - -select { - border: 1px solid #ccc; - border-radius: 4px; - padding: 5px 6px; - margin-top: 0; - color: var(--body-fg); - background-color: #292929; -} - -#nav-sidebar { - z-index: 15; - flex: 0 0 275px; - left: -276px; - margin-left: -276px; - border-top: 1px solid transparent; - border-right: 1px solid var(--hairline-color); - background-color: #00000000; - overflow: auto; -} - -.toggle-nav-sidebar { - z-index: 20; - left: 0; - display: flex; - align-items: center; - justify-content: center; - flex: 0 0 23px; - width: 23px; - border: 0; - border-right: 1px solid var(--hairline-color); - background-color: #00000000; - cursor: pointer; - font-size: 1.25rem; - color: var(--link-fg); - padding: 0; -} - -tr:nth-child(odd), .row-form-error { - background: #282828; -} -tr:nth-child(even) { - background: #1d1d1dd6; -} - -#nav-filter.no-results { - background: #00000000; -} -.errornote { - font-size: 0.875rem; - font-weight: 700; - display: block; - padding: 10px 12px; - margin: 0 0 10px 0; - color: #cf1414; - border: 1px solid #ba2121; - border-radius: 4px; - background-color: #292929; - background-position: 5px 12px; - overflow-wrap: break-word; -} -ul.errorlist { - margin: 0 0 4px; - padding: 3px; - color: #f81919; - background: #292929; -} -ul.errorlist li { - font-size: 0.8125rem; - display: block; - margin-bottom: 1px; -} -.errors input, .errors select, .errors textarea, td ul.errorlist + input, td ul.errorlist + select, td ul.errorlist + textarea{ - border: 1px solid var(--error-fg); -} - -div.breadcrumbs a { - color: #ffffffd6; - font-style: normal; - -} - -div.breadcrumbs { - background: #533493; - padding: 10px 40px; - border: none; - color: #ffffff; - text-align: left; - font-style: italic; -} -td, th { - font-size: 0.8125rem; - line-height: 16px; - vertical-align: top; - padding: 8px; - diff --git a/templates/admin/app_list_nav_change.html b/templates/admin/app_list_nav_change.html deleted file mode 100644 index 4db908c2..00000000 --- a/templates/admin/app_list_nav_change.html +++ /dev/null @@ -1,40 +0,0 @@ -{% load i18n %} - -{% if app_list %} - {% for app in app_list %} -
- - - {% for model in app.models %} - - {% if model.admin_url %} - - {% else %} - - {% endif %} - - {% if model.add_url %} - - {% else %} - - {% endif %} - - {% if model.admin_url and show_changelinks %} - {% if model.view_only %} - - {% else %} - - {% endif %} - {% elif show_changelinks %} - - {% endif %} - - {% endfor %} - -
- {% endfor %} -{% else %} -

{% translate 'You don’t have permission to view or edit anything.' %}

-{% endif %} diff --git a/templates/admin/base_site.html b/templates/admin/base_site.html deleted file mode 100644 index aca74f9a..00000000 --- a/templates/admin/base_site.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "admin/base.html" %} -{% load static %} - -{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}ProColLab| {{ site_title|default:_('Django site admin') }}{% endblock %} - -{% block extrastyle %} - - -{% endblock %} - -{% block branding %} -

ProColLab

-{% endblock %} - -{% block nav-global %}Петр Птушкин edition{% endblock %} diff --git a/templates/admin/change_form.html b/templates/admin/change_form.html deleted file mode 100644 index 934d64d1..00000000 --- a/templates/admin/change_form.html +++ /dev/null @@ -1,81 +0,0 @@ -{% extends "admin/base_site.html" %} -{% load i18n admin_urls static admin_modify %} -{% load static %} -{% block extrahead %}{{ block.super }} - -{{ media }} -{% endblock %} - -{% block extrastyle %}{{ block.super }}{% endblock %} - -{% block coltype %}colM{% endblock %} - -{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-form{% endblock %} - -{% if not is_popup %} -{% block breadcrumbs %} - -{% endblock %} -{% endif %} - -{% block content %}
-{% block object-tools %} -{% if change %}{% if not is_popup %} -
    - {% block object-tools-items %} - {% change_form_object_tools %} - {% endblock %} -
-{% endif %}{% endif %} -{% endblock %} -
{% csrf_token %}{% block form_top %}{% endblock %} -
-{% if is_popup %}{% endif %} -{% if to_field %}{% endif %} -{% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %} -{% if errors %} -

- {% if errors|length == 1 %}{% translate "Please correct the error below." %}{% else %}{% translate "Please correct the errors below." %}{% endif %} -

- {{ adminform.form.non_field_errors }} -{% endif %} - -{% block field_sets %} -{% for fieldset in adminform %} - {% include "admin/includes/fieldset.html" %} -{% endfor %} -{% endblock %} - -{% block after_field_sets %}{% endblock %} - -{% block inline_field_sets %} -{% for inline_admin_formset in inline_admin_formsets %} - {% include inline_admin_formset.opts.template %} -{% endfor %} -{% endblock %} - -{% block after_related_objects %}{% endblock %} - -{% block submit_buttons_bottom %}{% submit_row %}{% endblock %} - -{% block admin_change_form_document_ready %} - -{% endblock %} - -{# JavaScript for prepopulated fields #} -{% prepopulated_fields_js %} - -
-
-{% endblock %} diff --git a/templates/admin/change_list.html b/templates/admin/change_list.html deleted file mode 100644 index a67a4544..00000000 --- a/templates/admin/change_list.html +++ /dev/null @@ -1,88 +0,0 @@ -{% extends "admin/base_site.html" %} -{% load i18n admin_urls static admin_list %} -{% load static %} - -{% block extrastyle %} - {{ block.super }} - - {% if cl.formset %} - - {% endif %} - {% if cl.formset or action_form %} - - {% endif %} - {{ media.css }} - {% if not actions_on_top and not actions_on_bottom %} - - {% endif %} -{% endblock %} - -{% block extrahead %} -{{ block.super }} -{{ media.js }} - -{% endblock %} - -{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-list{% endblock %} - -{% if not is_popup %} -{% block breadcrumbs %} - -{% endblock %} -{% endif %} - -{% block coltype %}{% endblock %} - -{% block content %} -
- {% block object-tools %} -
    - {% block object-tools-items %} - {% change_list_object_tools %} - {% endblock %} -
- {% endblock %} - {% if cl.formset and cl.formset.errors %} -

- {% if cl.formset.total_error_count == 1 %}{% translate "Please correct the error below." %}{% else %}{% translate "Please correct the errors below." %}{% endif %} -

- {{ cl.formset.non_form_errors }} - {% endif %} -
-
- {% block search %}{% search_form cl %}{% endblock %} - {% block date_hierarchy %}{% if cl.date_hierarchy %}{% date_hierarchy cl %}{% endif %}{% endblock %} - -
{% csrf_token %} - {% if cl.formset %} -
{{ cl.formset.management_form }}
- {% endif %} - - {% block result_list %} - {% if action_form and actions_on_top and cl.show_admin_actions %}{% admin_actions %}{% endif %} - {% result_list cl %} - {% if action_form and actions_on_bottom and cl.show_admin_actions %}{% admin_actions %}{% endif %} - {% endblock %} - {% block pagination %}{% pagination cl %}{% endblock %} -
-
- {% block filters %} - {% if cl.has_filters %} -
-

{% translate 'Filter' %}

- {% if cl.has_active_filters %}

- ✖ {% translate "Clear all filters" %} -

{% endif %} - {% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %} -
- {% endif %} - {% endblock %} -
-
-{% endblock %} diff --git a/templates/admin/index.html b/templates/admin/index.html deleted file mode 100644 index 7837bf77..00000000 --- a/templates/admin/index.html +++ /dev/null @@ -1,50 +0,0 @@ -{% extends "admin/base_site.html" %} -{% load i18n static %} - -{% block extrastyle %}{{ block.super }}{% endblock %} - -{% block coltype %}colMS{% endblock %} - -{% block bodyclass %}{{ block.super }} dashboard{% endblock %} - -{% block breadcrumbs %}{% endblock %} - -{% block nav-sidebar %}{% endblock %} - -{% block content %} -
- {% include "admin/app_list_nav_change.html" with app_list=app_list show_changelinks=True %} -
-{% endblock %} - -{% block sidebar %} - -{% endblock %} diff --git a/templates/admin/login.html b/templates/admin/login.html deleted file mode 100644 index efd863d7..00000000 --- a/templates/admin/login.html +++ /dev/null @@ -1,68 +0,0 @@ -{% extends "admin/base_site.html" %} -{% load i18n static %} - -{% block extrastyle %}{{ block.super }} -{{ form.media }} -{% endblock %} - -{% block bodyclass %}{{ block.super }} login{% endblock %} - -{% block usertools %}{% endblock %} - -{% block nav-global %}{% endblock %} - -{% block nav-sidebar %}{% endblock %} - -{% block content_title %}{% endblock %} - -{% block breadcrumbs %}{% endblock %} - -{% block content %} -{% if form.errors and not form.non_field_errors %} -

-{% if form.errors.items|length == 1 %}{% translate "Please correct the error below." %}{% else %}{% translate "Please correct the errors below." %}{% endif %} -

-{% endif %} - -{% if form.non_field_errors %} -{% for error in form.non_field_errors %} -

- {{ error }} -

-{% endfor %} -{% endif %} - -
- -{% if user.is_authenticated %} -

-{% blocktranslate trimmed %} - You are authenticated as {{ username }}, but are not authorized to - access this page. Would you like to login to a different account? -{% endblocktranslate %} -

-{% endif %} - -
{% csrf_token %} -
- {{ form.username.errors }} - {{ form.username.label_tag }} {{ form.username }} -
-
- {{ form.password.errors }} - {{ form.password.label_tag }} {{ form.password }} - -
- {% url 'admin_password_reset' as password_reset_url %} - {% if password_reset_url %} - - {% endif %} -
- -
-
- -
-{% endblock %} diff --git a/users/models.py b/users/models.py index 3315bf18..22ed7c7a 100644 --- a/users/models.py +++ b/users/models.py @@ -83,6 +83,7 @@ class CustomUser(AbstractUser): organization = models.CharField(max_length=255, null=True, blank=True) speciality = models.CharField(max_length=255, null=True, blank=True) # default=null - it means that onboarding is completed + # fixme: mb replace to ChoiceField or FSMField(Finite State Machine) onboarding_stage = models.PositiveSmallIntegerField( null=True, blank=True, diff --git a/users/services/verification.py b/users/services/verification.py new file mode 100644 index 00000000..628090bb --- /dev/null +++ b/users/services/verification.py @@ -0,0 +1,65 @@ +""" +A verification service that uses ClickUp API for creating tasks + +When the user confirms his email, the service creates a task in the ClickUp Space with information +about the user so that the moderator can manually check the information about him +""" + +import requests +from decouple import config +from django.contrib.auth import get_user_model + +User = get_user_model() + + +class VerificationTasks: + instance = None + __TOKEN = config("CLICKUP_API_TOKEN", default="default-api-key", cast=str) + __CLICKUP_SPACE_ID = config("CLICKUP_SPACE_ID", cast=int) + + @classmethod + def create(cls, user: User): + data = cls._collect_data(user) + cls._send_request(data) + + @classmethod + def _collect_data(cls, user: User): + # todo DEBUG mode + link_to_platform = f"https://app.procollab.ru/office/profile/{user.pk}/" + link_to_admin_panel = ( + f"https://api.procollab.ru/admin/users/customuser/{user.pk}/change/" + ) + + priority = 3 # normal + if user.user_type != User.MEMBER: + priority = 1 # urgent + + description = f"Профиль в админке: {link_to_admin_panel}\nПрофиль на платформе: {link_to_platform}" + name = f"{user.pk}: {user.first_name} {user.last_name}" + + return { + "name": name, + "priority": priority, + "description": description, + } + + @classmethod + def _send_request(cls, data): + try: + url = f"https://api.clickup.com/api/v2/list/{cls.__CLICKUP_SPACE_ID}/task/" + requests.post( + url, + data=data, + headers={ + "Authorization": cls.__TOKEN, + }, + ).json() + except Exception: + # fixme + pass + + +def __new__(cls, *args, **kwargs): + if not hasattr(cls, "instance"): + cls.instance = super().__new__(cls) + return cls.instance diff --git a/users/views.py b/users/views.py index dc525f87..d44aec87 100644 --- a/users/views.py +++ b/users/views.py @@ -48,6 +48,7 @@ VerifyEmailSerializer, ) from .filters import UserFilter +from .services.verification import VerificationTasks User = get_user_model() Project = apps.get_model("projects", "Project") @@ -339,14 +340,26 @@ def get_queryset(self): class SetUserOnboardingStage(APIView): def put(self, request: Request, pk): try: - request.user.onboarding_stage = request.data["onboarding_stage"] - request.user.save() - # print(request.user.pk, pk) if request.user.pk != pk: return Response( status=status.HTTP_403_FORBIDDEN, data={"error": "You cannot edit other users!"}, ) + + new_stage = request.data["onboarding_stage"] + + if new_stage not in [None, *range(1, 4)]: + return Response( + status=status.HTTP_400_BAD_REQUEST, + data={"error": "Wrong onboarding stage number!"}, + ) + # if the user was on the last stage and passed it + if request.user.onboarding_stage == 3 and new_stage is None: + VerificationTasks.create(request.user) + + request.user.onboarding_stage = new_stage + request.user.save() + serialized_user = UserListSerializer(request.user) data = serialized_user.data return Response(status=status.HTTP_200_OK, data=data) From e283fde3b1a23248b163b08675a9355ba9ff6281 Mon Sep 17 00:00:00 2001 From: Yakser Date: Mon, 1 May 2023 13:51:08 +0300 Subject: [PATCH 3/5] Added default for `CLICKUP_SPACE_ID` --- users/services/verification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/services/verification.py b/users/services/verification.py index 628090bb..271223a5 100644 --- a/users/services/verification.py +++ b/users/services/verification.py @@ -15,7 +15,7 @@ class VerificationTasks: instance = None __TOKEN = config("CLICKUP_API_TOKEN", default="default-api-key", cast=str) - __CLICKUP_SPACE_ID = config("CLICKUP_SPACE_ID", cast=int) + __CLICKUP_SPACE_ID = config("CLICKUP_SPACE_ID", default="default-space-id", cast=int) @classmethod def create(cls, user: User): From fb65447894c0ca429f60ea44af51be78fd9d0fac Mon Sep 17 00:00:00 2001 From: Yakser Date: Mon, 1 May 2023 13:52:29 +0300 Subject: [PATCH 4/5] Fixed default for `CLICKUP_SPACE_ID` --- users/services/verification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/services/verification.py b/users/services/verification.py index 271223a5..68c264b3 100644 --- a/users/services/verification.py +++ b/users/services/verification.py @@ -15,7 +15,7 @@ class VerificationTasks: instance = None __TOKEN = config("CLICKUP_API_TOKEN", default="default-api-key", cast=str) - __CLICKUP_SPACE_ID = config("CLICKUP_SPACE_ID", default="default-space-id", cast=int) + __CLICKUP_SPACE_ID = config("CLICKUP_SPACE_ID", default=0, cast=int) @classmethod def create(cls, user: User): From a17657f21458388f38b0117b5465e531ea81c70d Mon Sep 17 00:00:00 2001 From: Yakser Date: Mon, 1 May 2023 14:05:22 +0300 Subject: [PATCH 5/5] Fixed indent in `VerificationTasks` class --- users/services/verification.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/users/services/verification.py b/users/services/verification.py index 68c264b3..5a26f120 100644 --- a/users/services/verification.py +++ b/users/services/verification.py @@ -58,8 +58,7 @@ def _send_request(cls, data): # fixme pass - -def __new__(cls, *args, **kwargs): - if not hasattr(cls, "instance"): - cls.instance = super().__new__(cls) - return cls.instance + def __new__(cls, *args, **kwargs): + if not hasattr(cls, "instance"): + cls.instance = super().__new__(cls) + return cls.instance