From 196ab0c5436f0dbca46e30e5c21fe50682be2cfb Mon Sep 17 00:00:00 2001 From: Krrish Sehgal <133865424+krrish-sehgal@users.noreply.github.com> Date: Fri, 24 Jan 2025 12:20:14 +0530 Subject: [PATCH] pre on staged changes --- .env.example | 7 +- blt/urls.py | 14 +- poetry.lock | 75 +- pyproject.toml | 14 +- .../0181_slackintegration_welcome_message.py | 21 + website/migrations/0182_project_status.py | 27 + .../migrations/0183_merge_20250124_0618.py | 12 + website/models.py | 126 +-- website/templates/feature_suggestion.html | 414 +++++----- website/templates/includes/header.html | 73 +- .../organization/add_slack_integration.html | 47 ++ website/templates/search.html | 753 +++++++++++++++--- website/test_slack.py | 173 ++-- website/views/company.py | 152 ++-- website/views/core.py | 135 ++-- website/views/slack_handlers.py | 414 +++++++--- website/views/slackbot.py | 394 +++++++++ 17 files changed, 2072 insertions(+), 779 deletions(-) create mode 100644 website/migrations/0181_slackintegration_welcome_message.py create mode 100644 website/migrations/0182_project_status.py create mode 100644 website/migrations/0183_merge_20250124_0618.py create mode 100644 website/views/slackbot.py diff --git a/.env.example b/.env.example index 2dd50371d..2867dfebe 100644 --- a/.env.example +++ b/.env.example @@ -27,9 +27,10 @@ DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGR #Sentry DSN SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0 -SLACK_CLIENT_ID= -SLACK_CLIENT_SECRET= - +SLACK_ID_CLIENT=your_slack_client_id_here +SLACK_SECRET_CLIENT=your_slack_client_secret_here +SLACK_BOT_TOKEN=your_slack_bot_token_here +SLACK_SIGNING_SECRET=your_slack_signing_secret_here #BlueSky User Details BLUESKY_USERNAME=example.bsky.social diff --git a/blt/urls.py b/blt/urls.py index 421f94fd6..cdacbd47a 100644 --- a/blt/urls.py +++ b/blt/urls.py @@ -35,13 +35,7 @@ UserIssueViewSet, UserProfileViewSet, ) -from website.views.blog import ( - PostCreateView, - PostDeleteView, - PostDetailView, - PostListView, - PostUpdateView, -) +from website.views.blog import PostCreateView, PostDeleteView, PostDetailView, PostListView, PostUpdateView from website.views.company import ( AddDomainView, AddHuntView, @@ -186,7 +180,7 @@ distribute_bacon, select_contribution, ) -from website.views.slack_handlers import slack_events +from website.views.slack_handlers import slack_commands, slack_events from website.views.teams import ( TeamChallenges, TeamLeaderboard, @@ -292,7 +286,8 @@ re_path(r"^auth/github/connect/$", GithubConnect.as_view(), name="github_connect"), re_path(r"^auth/google/connect/$", GoogleConnect.as_view(), name="google_connect"), path("auth/github/url/", github_views.oauth2_login), - path("oauth/slack/callback/", SlackCallbackView.as_view(), name="slack_callback"), + path("oauth/slack/callback/", SlackCallbackView.as_view(), name="slack_oauth_callback"), + path("slack/commands/", slack_commands, name="slack_commands"), path("auth/google/url/", google_views.oauth2_login), path("auth/facebook/url/", facebook_views.oauth2_callback), path("socialaccounts/", SocialAccountListView.as_view(), name="social_account_list"), @@ -860,7 +855,6 @@ name="similarity_scan", ), path("projects/create/", create_project, name="create_project"), - path("teams/challenges/", TeamChallenges.as_view(), name="team_challenges"), path("teams/leaderboard/", TeamLeaderboard.as_view(), name="team_leaderboard"), path("challenges/", UserChallengeListView.as_view(), name="user_challenges"), diff --git a/poetry.lock b/poetry.lock index 87a16f4ca..be1d6b67f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1220,18 +1220,19 @@ hiredis = ["redis[hiredis] (>=3,!=4.0.0,!=4.0.1)"] [[package]] name = "django-simple-captcha" -version = "0.6.0" +version = "0.6.1" description = "A very simple, yet powerful, Django captcha application" optional = false python-versions = "*" files = [ - {file = "django-simple-captcha-0.6.0.tar.gz", hash = "sha256:d188516d326fadd2d5ad076eb89649d55c02cabafe3fdcc2154ac18e9f6d4b97"}, - {file = "django_simple_captcha-0.6.0-py2.py3-none-any.whl", hash = "sha256:3ae9a7e650cb0cdbcfd4a75aa91fdf25dcc523ef541a7b1f004bd4357798fc03"}, + {file = "django_simple_captcha-0.6.1-py2.py3-none-any.whl", hash = "sha256:75c4a89cf54011be54dd96fc4bfc2286f92bed88c46d247b9e504b7fc4c7cfe5"}, + {file = "django_simple_captcha-0.6.1.tar.gz", hash = "sha256:9df5cf24e38b91af62d601f8e5189b0f6d28dbaa55a363a8b9d727703f9c35de"}, ] [package.dependencies] Django = ">=4.2" django-ranged-response = "0.2.0" +djangorestframework = ">=3.15.0" Pillow = ">=6.2.0" [package.extras] @@ -4005,29 +4006,29 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.9.2" +version = "0.9.3" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.9.2-py3-none-linux_armv6l.whl", hash = "sha256:80605a039ba1454d002b32139e4970becf84b5fee3a3c3bf1c2af6f61a784347"}, - {file = "ruff-0.9.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b9aab82bb20afd5f596527045c01e6ae25a718ff1784cb92947bff1f83068b00"}, - {file = "ruff-0.9.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fbd337bac1cfa96be615f6efcd4bc4d077edbc127ef30e2b8ba2a27e18c054d4"}, - {file = "ruff-0.9.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b35259b0cbf8daa22a498018e300b9bb0174c2bbb7bcba593935158a78054d"}, - {file = "ruff-0.9.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b6a9701d1e371bf41dca22015c3f89769da7576884d2add7317ec1ec8cb9c3c"}, - {file = "ruff-0.9.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cc53e68b3c5ae41e8faf83a3b89f4a5d7b2cb666dff4b366bb86ed2a85b481f"}, - {file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8efd9da7a1ee314b910da155ca7e8953094a7c10d0c0a39bfde3fcfd2a015684"}, - {file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3292c5a22ea9a5f9a185e2d131dc7f98f8534a32fb6d2ee7b9944569239c648d"}, - {file = "ruff-0.9.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a605fdcf6e8b2d39f9436d343d1f0ff70c365a1e681546de0104bef81ce88df"}, - {file = "ruff-0.9.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c547f7f256aa366834829a08375c297fa63386cbe5f1459efaf174086b564247"}, - {file = "ruff-0.9.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d18bba3d3353ed916e882521bc3e0af403949dbada344c20c16ea78f47af965e"}, - {file = "ruff-0.9.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b338edc4610142355ccf6b87bd356729b62bf1bc152a2fad5b0c7dc04af77bfe"}, - {file = "ruff-0.9.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:492a5e44ad9b22a0ea98cf72e40305cbdaf27fac0d927f8bc9e1df316dcc96eb"}, - {file = "ruff-0.9.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:af1e9e9fe7b1f767264d26b1075ac4ad831c7db976911fa362d09b2d0356426a"}, - {file = "ruff-0.9.2-py3-none-win32.whl", hash = "sha256:71cbe22e178c5da20e1514e1e01029c73dc09288a8028a5d3446e6bba87a5145"}, - {file = "ruff-0.9.2-py3-none-win_amd64.whl", hash = "sha256:c5e1d6abc798419cf46eed03f54f2e0c3adb1ad4b801119dedf23fcaf69b55b5"}, - {file = "ruff-0.9.2-py3-none-win_arm64.whl", hash = "sha256:a1b63fa24149918f8b37cef2ee6fff81f24f0d74b6f0bdc37bc3e1f2143e41c6"}, - {file = "ruff-0.9.2.tar.gz", hash = "sha256:b5eceb334d55fae5f316f783437392642ae18e16dcf4f1858d55d3c2a0f8f5d0"}, + {file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"}, + {file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"}, + {file = "ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b"}, + {file = "ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c"}, + {file = "ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4"}, + {file = "ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b"}, + {file = "ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a"}, ] [[package]] @@ -4110,13 +4111,13 @@ wrapt = ">=1.10,<2.0" [[package]] name = "selenium" -version = "4.27.1" +version = "4.28.1" description = "Official Python bindings for Selenium WebDriver" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "selenium-4.27.1-py3-none-any.whl", hash = "sha256:b89b1f62b5cfe8025868556fe82360d6b649d464f75d2655cb966c8f8447ea18"}, - {file = "selenium-4.27.1.tar.gz", hash = "sha256:5296c425a75ff1b44d0d5199042b36a6d1ef76c04fb775b97b40be739a9caae2"}, + {file = "selenium-4.28.1-py3-none-any.whl", hash = "sha256:4238847e45e24e4472cfcf3554427512c7aab9443396435b1623ef406fff1cc1"}, + {file = "selenium-4.28.1.tar.gz", hash = "sha256:0072d08670d7ec32db901bd0107695a330cecac9f196e3afb3fa8163026e022a"}, ] [package.dependencies] @@ -4322,13 +4323,13 @@ files = [ [[package]] name = "tablib" -version = "3.7.0" +version = "3.8.0" description = "Format agnostic tabular data library (XLS, JSON, YAML, CSV, etc.)" optional = false python-versions = ">=3.9" files = [ - {file = "tablib-3.7.0-py3-none-any.whl", hash = "sha256:9a6930037cfe0f782377963ca3f2b1dae3fd4cdbf0883848f22f1447e7bb718b"}, - {file = "tablib-3.7.0.tar.gz", hash = "sha256:f9db84ed398df5109bd69c11d46613d16cc572fb9ad3213f10d95e2b5f12c18e"}, + {file = "tablib-3.8.0-py3-none-any.whl", hash = "sha256:35bdb9d4ec7052232f8803908f9c7a9c3c65807188b70618fa7a7d8ccd560b4d"}, + {file = "tablib-3.8.0.tar.gz", hash = "sha256:94d8bcdc65a715a0024a6d5b701a5f31e45bd159269e62c73731de79f048db2b"}, ] [package.extras] @@ -4508,13 +4509,13 @@ files = [ [[package]] name = "unstructured" -version = "0.16.13" +version = "0.16.15" description = "A library that prepares raw documents for downstream ML tasks." optional = false python-versions = "<3.13,>=3.9.0" files = [ - {file = "unstructured-0.16.13-py3-none-any.whl", hash = "sha256:d578d3ebd78c6bf3ea837a13b7e2942671920f9e7361e8532c5eb00f9cf359e6"}, - {file = "unstructured-0.16.13.tar.gz", hash = "sha256:6195744a203e65bf6b8460cbfccd9bef67a1f5d44e79229a13e7e37f528abbcd"}, + {file = "unstructured-0.16.15-py3-none-any.whl", hash = "sha256:5b0931eb92fb858b983fada18111efdf9c2a0c861ef8e9b58c4e05b1daa50e35"}, + {file = "unstructured-0.16.15.tar.gz", hash = "sha256:18fb850d47b5a2a6ea45b2f7e0eda687f903a2f2e58909b1defd48e2b3126ff4"}, ] [package.dependencies] @@ -4542,19 +4543,19 @@ unstructured-client = "*" wrapt = "*" [package.extras] -all-docs = ["effdet", "google-cloud-vision", "markdown", "networkx", "onnx", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypandoc", "pypdf", "python-docx (>=1.1.2)", "python-pptx (>=1.0.1)", "unstructured-inference (==0.8.1)", "unstructured.pytesseract (>=0.3.12)", "xlrd"] +all-docs = ["effdet", "google-cloud-vision", "markdown", "networkx", "onnx", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypandoc", "pypdf", "python-docx (>=1.1.2)", "python-pptx (>=1.0.1)", "unstructured-inference (>=0.8.6)", "unstructured.pytesseract (>=0.3.12)", "xlrd"] csv = ["pandas"] doc = ["python-docx (>=1.1.2)"] docx = ["python-docx (>=1.1.2)"] epub = ["pypandoc"] huggingface = ["langdetect", "sacremoses", "sentencepiece", "torch", "transformers"] -image = ["effdet", "google-cloud-vision", "onnx", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypdf", "unstructured-inference (==0.8.1)", "unstructured.pytesseract (>=0.3.12)"] -local-inference = ["effdet", "google-cloud-vision", "markdown", "networkx", "onnx", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypandoc", "pypdf", "python-docx (>=1.1.2)", "python-pptx (>=1.0.1)", "unstructured-inference (==0.8.1)", "unstructured.pytesseract (>=0.3.12)", "xlrd"] +image = ["effdet", "google-cloud-vision", "onnx", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypdf", "unstructured-inference (>=0.8.6)", "unstructured.pytesseract (>=0.3.12)"] +local-inference = ["effdet", "google-cloud-vision", "markdown", "networkx", "onnx", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypandoc", "pypdf", "python-docx (>=1.1.2)", "python-pptx (>=1.0.1)", "unstructured-inference (>=0.8.6)", "unstructured.pytesseract (>=0.3.12)", "xlrd"] md = ["markdown"] odt = ["pypandoc", "python-docx (>=1.1.2)"] org = ["pypandoc"] paddleocr = ["paddlepaddle (==3.0.0b1)", "unstructured.paddleocr (==2.8.1.0)"] -pdf = ["effdet", "google-cloud-vision", "onnx", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypdf", "unstructured-inference (==0.8.1)", "unstructured.pytesseract (>=0.3.12)"] +pdf = ["effdet", "google-cloud-vision", "onnx", "pdf2image", "pdfminer.six", "pi-heif", "pikepdf", "pypdf", "unstructured-inference (>=0.8.6)", "unstructured.pytesseract (>=0.3.12)"] ppt = ["python-pptx (>=1.0.1)"] pptx = ["python-pptx (>=1.0.1)"] rst = ["pypandoc"] @@ -5020,4 +5021,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "3.11.2" -content-hash = "780e52e5dabecef51e218b0095e719e55efd253cd2da92ce4c64c9412a66c18e" +content-hash = "9e92c532af81fecec4b0855a320481a779b88c8e456ec7d745c47d4be4790fbb" diff --git a/pyproject.toml b/pyproject.toml index 085a622ff..86f87c393 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ Unidecode = "^1.3.8" user-agents = "^2.2.0" whitenoise = "^6.8.2" django-debug-toolbar = "^4.4.6" -selenium = "^4.27.1" +selenium = "^4.28.1" pylibmc = "^1.6.1" psycopg2-binary = "^2.9.10" boto = "^2.49.0" @@ -43,7 +43,7 @@ pytz = "^2024.1" requests = "^2.32.3" requests-oauthlib = "^1.3.1" six = "^1.16.0" -tablib = "^3.2.0" +tablib = "^3.8.0" ua-parser = "^1.0.0" djangorestframework = "^3.15.2" cffi = "^1.17.1" @@ -56,7 +56,7 @@ stripe = "^8.4.0" django-environ = "^0.12.0" django-humanize = "^0.1.2" drf-yasg = "^1.21.8" -django-simple-captcha = "^0.6.0" +django-simple-captcha = "^0.6.1" django-filter = "^24.3" webdriver-manager = "^4.0.2" pillow = "^10.4.0" @@ -65,7 +65,7 @@ sentry-sdk = "^2.20.0" bitcash = "^1.0.2" pydantic = "^2.10.5" pydantic_core = "^2.18.4" -unstructured = "^0.16.13" +unstructured = "^0.16.15" Markdown = "^3.6" faiss-cpu = "^1.8.0" psutil = "^5.9.8" @@ -87,16 +87,16 @@ newrelic = "^10.4.0" [tool.poetry.group.dev.dependencies] black = "^24.8.0" isort = "^5.13.2" -ruff = "^0.9.2" +ruff = "^0.9.3" pre-commit = "^3.8.0" [tool.isort] known_first_party = ["blt"] -line_length = 100 +line_length = 120 profile = "black" [tool.ruff] -line-length = 100 +line-length = 120 target-version = "py311" [tool.ruff.lint] diff --git a/website/migrations/0181_slackintegration_welcome_message.py b/website/migrations/0181_slackintegration_welcome_message.py new file mode 100644 index 000000000..a75140843 --- /dev/null +++ b/website/migrations/0181_slackintegration_welcome_message.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1.3 on 2025-01-23 18:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("website", "0180_rename_project_visit_count_repo_repo_visit_count"), + ] + + operations = [ + migrations.AddField( + model_name="slackintegration", + name="welcome_message", + field=models.TextField( + blank=True, + help_text="Custom welcome message for new members. Use Slack markdown formatting.", + null=True, + ), + ), + ] diff --git a/website/migrations/0182_project_status.py b/website/migrations/0182_project_status.py new file mode 100644 index 000000000..eb7fbae8b --- /dev/null +++ b/website/migrations/0182_project_status.py @@ -0,0 +1,27 @@ +# Generated by Django 5.1.4 on 2025-01-24 03:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("website", "0181_slackintegration_welcome_message"), + ] + + operations = [ + migrations.AddField( + model_name="project", + name="status", + field=models.CharField( + choices=[ + ("flagship", "Flagship"), + ("production", "Production"), + ("incubator", "Incubator"), + ("lab", "Lab"), + ("inactive", "Inactive"), + ], + default="new", + max_length=20, + ), + ), + ] diff --git a/website/migrations/0183_merge_20250124_0618.py b/website/migrations/0183_merge_20250124_0618.py new file mode 100644 index 000000000..1dec887c1 --- /dev/null +++ b/website/migrations/0183_merge_20250124_0618.py @@ -0,0 +1,12 @@ +# Generated by Django 5.1.4 on 2025-01-24 06:18 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("website", "0180_merge_0178_merge_20241229_1948_0179_contributorstats"), + ("website", "0182_project_status"), + ] + + operations = [] diff --git a/website/models.py b/website/models.py index a1b190254..e7a751240 100644 --- a/website/models.py +++ b/website/models.py @@ -88,16 +88,10 @@ def __str__(self): class SlackIntegration(models.Model): - integration = models.OneToOneField( - Integration, on_delete=models.CASCADE, related_name="slack_integration" - ) - bot_access_token = models.CharField( - max_length=255, null=True, blank=True - ) # will be different for each workspace + integration = models.OneToOneField(Integration, on_delete=models.CASCADE, related_name="slack_integration") + bot_access_token = models.CharField(max_length=255, null=True, blank=True) # will be different for each workspace workspace_name = models.CharField(max_length=255, null=True, blank=True) - default_channel_name = models.CharField( - max_length=255, null=True, blank=True - ) # Default channel ID + default_channel_name = models.CharField(max_length=255, null=True, blank=True) # Default channel ID default_channel_id = models.CharField(max_length=255, null=True, blank=True) daily_updates = models.BooleanField(default=False) daily_update_time = models.IntegerField( @@ -106,6 +100,12 @@ class SlackIntegration(models.Model): validators=[MinValueValidator(0), MaxValueValidator(23)], # Valid hours: 0–23 help_text="The hour of the day (0-23) to send daily updates", ) + # Add welcome message field + welcome_message = models.TextField( + null=True, + blank=True, + help_text="Custom welcome message for new members. Use Slack markdown formatting.", + ) def __str__(self): return f"Slack Integration for {self.integration.organization.name}" @@ -187,12 +187,7 @@ def closed_issues(self): @property def top_tester(self): - return ( - User.objects.filter(issue__domain=self) - .annotate(total=Count("issue")) - .order_by("-total") - .first() - ) + return User.objects.filter(issue__domain=self).annotate(total=Count("issue")).order_by("-total").first() @property def get_name(self): @@ -309,9 +304,7 @@ class HuntPrize(models.Model): name = models.CharField(max_length=50) value = models.PositiveIntegerField(default=0) no_of_eligible_projects = models.PositiveIntegerField(default=1) # no of winner in this prize - valid_submissions_eligible = models.BooleanField( - default=False - ) # all valid submissions are winners in this prize + valid_submissions_eligible = models.BooleanField(default=False) # all valid submissions are winners in this prize prize_in_crypto = models.BooleanField(default=False) description = models.TextField(null=True, blank=True) created = models.DateTimeField(auto_now_add=True) @@ -346,12 +339,8 @@ class Issue(models.Model): status = models.CharField(max_length=10, default="open", null=True, blank=True) user_agent = models.CharField(max_length=255, default="", null=True, blank=True) ocr = models.TextField(default="", null=True, blank=True) - screenshot = models.ImageField( - upload_to="screenshots", null=True, blank=True, validators=[validate_image] - ) - closed_by = models.ForeignKey( - User, null=True, blank=True, related_name="closed_by", on_delete=models.CASCADE - ) + screenshot = models.ImageField(upload_to="screenshots", null=True, blank=True, validators=[validate_image]) + closed_by = models.ForeignKey(User, null=True, blank=True, related_name="closed_by", on_delete=models.CASCADE) closed_date = models.DateTimeField(default=None, null=True, blank=True) github_url = models.URLField(default="", null=True, blank=True) created = models.DateTimeField(auto_now_add=True) @@ -394,9 +383,7 @@ def get_twitter_message(self): prefix + self.domain_title + spacer - + self.description[ - : 140 - (len(prefix) + len(self.domain_title) + len(spacer) + len(issue_link)) - ] + + self.description[: 140 - (len(prefix) + len(self.domain_title) + len(spacer) + len(issue_link))] + issue_link ) return msg @@ -456,9 +443,7 @@ def delete_image_on_issue_delete(sender, instance, **kwargs): except NotFound: logger.warning(f"File not found in Google Cloud Storage: {blob_name}") except Exception as e: - logger.error( - f"Error deleting image from Google Cloud Storage: {blob_name} - {str(e)}" - ) + logger.error(f"Error deleting image from Google Cloud Storage: {blob_name} - {str(e)}") else: @@ -490,9 +475,7 @@ def delete_image_on_post_delete(sender, instance, **kwargs): except NotFound: logger.warning(f"File not found in Google Cloud Storage: {blob_name}") except Exception as e: - logger.error( - f"Error deleting image from Google Cloud Storage: {blob_name} - {str(e)}" - ) + logger.error(f"Error deleting image from Google Cloud Storage: {blob_name} - {str(e)}") else: @@ -524,12 +507,8 @@ def update_issue_image_access(sender, instance, **kwargs): class Winner(models.Model): hunt = models.ForeignKey(Hunt, null=True, blank=True, on_delete=models.CASCADE) - winner = models.ForeignKey( - User, related_name="winner", null=True, blank=True, on_delete=models.CASCADE - ) - runner = models.ForeignKey( - User, related_name="runner", null=True, blank=True, on_delete=models.CASCADE - ) + winner = models.ForeignKey(User, related_name="winner", null=True, blank=True, on_delete=models.CASCADE) + runner = models.ForeignKey(User, related_name="runner", null=True, blank=True, on_delete=models.CASCADE) second_runner = models.ForeignKey( User, related_name="second_runner", @@ -595,12 +574,8 @@ class UserProfile(models.Model): issue_flaged = models.ManyToManyField(Issue, blank=True, related_name="flaged") issues_hidden = models.BooleanField(default=False) - subscribed_domains = models.ManyToManyField( - Domain, related_name="user_subscribed_domains", blank=True - ) - subscribed_users = models.ManyToManyField( - User, related_name="user_subscribed_users", blank=True - ) + subscribed_domains = models.ManyToManyField(Domain, related_name="user_subscribed_domains", blank=True) + subscribed_users = models.ManyToManyField(User, related_name="user_subscribed_users", blank=True) btc_address = models.CharField(max_length=100, blank=True, null=True) bch_address = models.CharField(max_length=100, blank=True, null=True) eth_address = models.CharField(max_length=100, blank=True, null=True) @@ -652,9 +627,7 @@ def update_streak_and_award_points(self, check_in_date=None): try: with transaction.atomic(): # Streak logic - if not self.last_check_in or check_in_date == self.last_check_in + timedelta( - days=1 - ): + if not self.last_check_in or check_in_date == self.last_check_in + timedelta(days=1): self.current_streak += 1 self.longest_streak = max(self.current_streak, self.longest_streak) # If check-in is not consecutive, reset streak @@ -770,9 +743,7 @@ class Wallet(models.Model): created = models.DateTimeField(auto_now_add=True) def deposit(self, value): - self.transaction_set.create( - value=value, running_balance=self.current_balance + Decimal(value) - ) + self.transaction_set.create(value=value, running_balance=self.current_balance + Decimal(value)) self.current_balance += Decimal(value) self.save() @@ -780,9 +751,7 @@ def withdraw(self, value): if value > self.current_balance: raise Exception("This wallet has insufficient balance.") - self.transaction_set.create( - value=-value, running_balance=self.current_balance - Decimal(value) - ) + self.transaction_set.create(value=-value, running_balance=self.current_balance - Decimal(value)) self.current_balance -= Decimal(value) self.save() @@ -900,6 +869,14 @@ def __str__(self): class Project(models.Model): + STATUS_CHOICES = [ + ("flagship", "Flagship"), + ("production", "Production"), + ("incubator", "Incubator"), + ("lab", "Lab"), + ("inactive", "Inactive"), + ] + organization = models.ForeignKey( Organization, null=True, @@ -910,9 +887,8 @@ class Project(models.Model): name = models.CharField(max_length=255) slug = models.SlugField(unique=True, blank=True) description = models.TextField() - url = models.URLField( - unique=True, null=True, blank=True - ) # Made url nullable in case of no website + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="new") + url = models.URLField(unique=True, null=True, blank=True) # Made url nullable in case of no website project_visit_count = models.IntegerField(default=0) twitter = models.CharField(max_length=30, null=True, blank=True) facebook = models.URLField(null=True, blank=True) @@ -953,9 +929,7 @@ class Contribution(models.Model): title = models.CharField(max_length=255) description = models.TextField() repository = models.ForeignKey(Project, on_delete=models.CASCADE, null=True) - contribution_type = models.CharField( - max_length=20, choices=CONTRIBUTION_TYPES, default="commit" - ) + contribution_type = models.CharField(max_length=20, choices=CONTRIBUTION_TYPES, default="commit") github_username = models.CharField(max_length=255, default="") github_id = models.CharField(max_length=100, null=True, blank=True) github_url = models.URLField(null=True, blank=True) @@ -976,9 +950,7 @@ class BaconToken(models.Model): amount = models.DecimalField(max_digits=10, decimal_places=2) created = models.DateTimeField(auto_now_add=True) contribution = models.OneToOneField(Contribution, on_delete=models.CASCADE) - token_id = models.CharField( - max_length=64, blank=True, null=True - ) # Token ID from the Runes protocol + token_id = models.CharField(max_length=64, blank=True, null=True) # Token ID from the Runes protocol def __str__(self): return f"{self.user.username} - {self.amount} BACON" @@ -1025,9 +997,7 @@ def clear_blocked_cache(sender, instance=None, **kwargs): class TimeLog(models.Model): - user = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="timelogs" - ) + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="timelogs") # associate organization with sizzle organization = models.ForeignKey( Organization, @@ -1052,9 +1022,7 @@ def __str__(self): class ActivityLog(models.Model): - user = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="activity_logs" - ) + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="activity_logs") window_title = models.CharField(max_length=255) url = models.URLField(null=True, blank=True) # URL field for activity-related URL recorded_at = models.DateTimeField(auto_now_add=True) @@ -1242,9 +1210,7 @@ def verify_file_upload(sender, instance, **kwargs): print(f"Checking if image '{instance.image.name}' exists in the storage backend...") if not default_storage.exists(instance.image.name): print(f"Image '{instance.image.name}' was not uploaded to the storage backend.") - raise ValidationError( - f"Image '{instance.image.name}' was not uploaded to the storage backend." - ) + raise ValidationError(f"Image '{instance.image.name}' was not uploaded to the storage backend.") class Repo(models.Model): @@ -1327,18 +1293,14 @@ class ContributorStats(models.Model): comments = models.PositiveIntegerField(default=0) # "day" for daily entries, "month" for monthly entries - granularity = models.CharField( - max_length=10, choices=[("day", "Day"), ("month", "Month")], default="day" - ) + granularity = models.CharField(max_length=10, choices=[("day", "Day"), ("month", "Month")], default="day") class Meta: # You can't have two different stats for the same date+granularity unique_together = ("contributor", "repo", "date", "granularity") def __str__(self): - return ( - f"{self.contributor.name} in {self.repo.name} " f"on {self.date} [{self.granularity}]" - ) + return f"{self.contributor.name} in {self.repo.name} " f"on {self.date} [{self.granularity}]" class Challenge(models.Model): @@ -1349,14 +1311,10 @@ class Challenge(models.Model): title = models.CharField(max_length=255) description = models.TextField() - challenge_type = models.CharField( - max_length=10, choices=CHALLENGE_TYPE_CHOICES, default="single" - ) + challenge_type = models.CharField(max_length=10, choices=CHALLENGE_TYPE_CHOICES, default="single") created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) - participants = models.ManyToManyField( - User, related_name="user_challenges", blank=True - ) # For single users + participants = models.ManyToManyField(User, related_name="user_challenges", blank=True) # For single users team_participants = models.ManyToManyField( Organization, related_name="team_challenges", blank=True ) # For team challenges diff --git a/website/templates/feature_suggestion.html b/website/templates/feature_suggestion.html index 28cfa29ea..a7deb10b9 100644 --- a/website/templates/feature_suggestion.html +++ b/website/templates/feature_suggestion.html @@ -14,146 +14,223 @@ -
- Description: {{ suggestion.description }} -
-- User:{{ suggestion.user }} -
-{{ suggestion.suggestion_id }}
-{{ suggestion.description }}
+{{ suggestion.suggestion_id }}
{{ domain.hostname_domain }}
{% endif %} ++ + Role: + {{ user.role|default:"Not specified" }} +
++ + Points: + {{ user.total_score }} +
+ {% if user.team %} ++ + Team: + {{ user.team }} +
+ {% endif %} +{{ organization.description|truncatechars:150 }}
+ {% endif %} ++ {% if organization.is_active %} + Active + {% else %} + Inactive + {% endif %} +
+{{ project.description|truncatechars:150 }}
+ {% endif %} +{{ repo.description|truncatechars:150 }}
{% endif %} +