diff --git a/project_channels.csv b/project_channels.csv new file mode 100644 index 0000000000..25dd13bd04 --- /dev/null +++ b/project_channels.csv @@ -0,0 +1,114 @@ +slack_channel,slack_id,slack_url +project-zap,C04SX2GAS,https://OWASP.slack.com/archives/C04SX2GAS +project-xenotix,C04T4HY7U,https://OWASP.slack.com/archives/C04T4HY7U +project-railsgoat,C04THC44W,https://OWASP.slack.com/archives/C04THC44W +project-o2,C04TJNC8M,https://OWASP.slack.com/archives/C04TJNC8M +project-nodegoat,C04TQK9UF,https://OWASP.slack.com/archives/C04TQK9UF +project-hackademic,C050BRC9M,https://OWASP.slack.com/archives/C050BRC9M +project-scg,C050V7CNL,https://OWASP.slack.com/archives/C050V7CNL +project-sec-shepherd,C051M1G3A,https://OWASP.slack.com/archives/C051M1G3A +project-dotnet,C053H58SK,https://OWASP.slack.com/archives/C053H58SK +project-zap-notify,C061VMC87,https://OWASP.slack.com/archives/C061VMC87 +project-asvs,C06MNF14M,https://OWASP.slack.com/archives/C06MNF14M +project-webgoat,C0948GVLM,https://OWASP.slack.com/archives/C0948GVLM +project-webgoat-notif,C09H06VFA,https://OWASP.slack.com/archives/C09H06VFA +project-zsc,C09HKQ0D7,https://OWASP.slack.com/archives/C09HKQ0D7 +project-devops,C09MLAY8P,https://OWASP.slack.com/archives/C09MLAY8P +project-wafec,C0BBA9FM0,https://OWASP.slack.com/archives/C0BBA9FM0 +project-hacakdemic,C0BR2NMUG,https://OWASP.slack.com/archives/C0BR2NMUG +project-skf,C0F7L9X6V,https://OWASP.slack.com/archives/C0F7L9X6V +project-csrfguard,C0H1KR347,https://OWASP.slack.com/archives/C0H1KR347 +project-glue,C0HVCFDP0,https://OWASP.slack.com/archives/C0HVCFDP0 +project-appsensor,C0KJ7JMCJ,https://OWASP.slack.com/archives/C0KJ7JMCJ +project-samm,C0VF1EJGH,https://OWASP.slack.com/archives/C0VF1EJGH +project-virtualvillag,C18A8EGKH,https://OWASP.slack.com/archives/C18A8EGKH +project-mobile-app-security,C1M6ZVC6S,https://OWASP.slack.com/archives/C1M6ZVC6S +project-vicnum,C1MAN1B08,https://OWASP.slack.com/archives/C1MAN1B08 +project-top-10,C1QBMGU69,https://OWASP.slack.com/archives/C1QBMGU69 +project-embeddedappsec,C1TJMUNG3,https://OWASP.slack.com/archives/C1TJMUNG3 +project-juiceshop,C255XSY04,https://OWASP.slack.com/archives/C255XSY04 +project-igoat,C2BKNP7DZ,https://OWASP.slack.com/archives/C2BKNP7DZ +project-blt,C2FF0UVHU,https://OWASP.slack.com/archives/C2FF0UVHU +project-olg-github,C3F2F9TMY,https://OWASP.slack.com/archives/C3F2F9TMY +project-riskrating,C56GPPD6Z,https://OWASP.slack.com/archives/C56GPPD6Z +project-riskrating_mp,C56GQ0ZHT,https://OWASP.slack.com/archives/C56GQ0ZHT +project-owtf,C5M114999,https://OWASP.slack.com/archives/C5M114999 +project-blt-github,C5QAK3Q9G,https://OWASP.slack.com/archives/C5QAK3Q9G +project-securityrat,C76U4TNFJ,https://OWASP.slack.com/archives/C76U4TNFJ +project-malware,C9G489878,https://OWASP.slack.com/archives/C9G489878 +project-securetea,C9GAF53NK,https://OWASP.slack.com/archives/C9GAF53NK +project-devslop,CA1PNFZSR,https://OWASP.slack.com/archives/CA1PNFZSR +project-mobile-app-security-dev,CCBAP0CGN,https://OWASP.slack.com/archives/CCBAP0CGN +project-sls-top-10,CD9D8J41E,https://OWASP.slack.com/archives/CD9D8J41E +project-scvs,CGH5X9NQ0,https://OWASP.slack.com/archives/CGH5X9NQ0 +project-packman,CHKT6HKTK,https://OWASP.slack.com/archives/CHKT6HKTK +project-security-bot,CLMA4F01J,https://OWASP.slack.com/archives/CLMA4F01J +project-mobile_tm,CLW9F9F0X,https://OWASP.slack.com/archives/CLW9F9F0X +project-integration,CPMEWT342,https://OWASP.slack.com/archives/CPMEWT342 +project-nettacker,CQZGG24FQ,https://OWASP.slack.com/archives/CQZGG24FQ +project-threat-dragon,CURE8PQ68,https://OWASP.slack.com/archives/CURE8PQ68 +project-pygoat,C013HSLMTFE,https://OWASP.slack.com/archives/C013HSLMTFE +project-blt-gsoc-rehndndup,C0145BH2P70,https://OWASP.slack.com/archives/C0145BH2P70 +project-samuraiwtf,C01524KH43G,https://OWASP.slack.com/archives/C01524KH43G +project-isvs,C01600RMP9P,https://OWASP.slack.com/archives/C01600RMP9P +project-off,C016U8XQ95H,https://OWASP.slack.com/archives/C016U8XQ95H +project-curriculum,C017AC06QV7,https://OWASP.slack.com/archives/C017AC06QV7 +project-sponsorship,C018P1JUPUH,https://OWASP.slack.com/archives/C018P1JUPUH +project-committee,C01930CGW23,https://OWASP.slack.com/archives/C01930CGW23 +project-how-to-get-into-appsec,C01KF26B1UH,https://OWASP.slack.com/archives/C01KF26B1UH +project-purpleteam,C01LARX6WP8,https://OWASP.slack.com/archives/C01LARX6WP8 +project-html-sanitizer,C0250DKTFCP,https://OWASP.slack.com/archives/C0250DKTFCP +project-developeroutreach,C02CXL4USFM,https://OWASP.slack.com/archives/C02CXL4USFM +project-cre,C02EAS3MY84,https://OWASP.slack.com/archives/C02EAS3MY84 +project-snow,C02EX68P1UJ,https://OWASP.slack.com/archives/C02EX68P1UJ +project-wrongsecrets,C02KQ7D9XHR,https://OWASP.slack.com/archives/C02KQ7D9XHR +project-pytm,C02KRQ0CATB,https://OWASP.slack.com/archives/C02KRQ0CATB +project-secure-code-review-guide,C02QDREE0M7,https://OWASP.slack.com/archives/C02QDREE0M7 +project-podcast,C02U3MTA13K,https://OWASP.slack.com/archives/C02U3MTA13K +project-iot-top10,C034JK2BFGW,https://OWASP.slack.com/archives/C034JK2BFGW +project-wrongsecrets-dev,C039L78LSER,https://OWASP.slack.com/archives/C039L78LSER +project-wrongsecrets-callback,C03BCJ1BXNK,https://OWASP.slack.com/archives/C03BCJ1BXNK +project-security-culture,C03CHLJ1YLR,https://OWASP.slack.com/archives/C03CHLJ1YLR +project-k8s-top10,C03FV6MSRCM,https://OWASP.slack.com/archives/C03FV6MSRCM +project-safetypes,C0432Q430Q3,https://OWASP.slack.com/archives/C0432Q430Q3 +project-continuous-penetration-testing-framework,C0484CAPBE0,https://OWASP.slack.com/archives/C0484CAPBE0 +project-domain-protect,C04BPJ5B2P4,https://OWASP.slack.com/archives/C04BPJ5B2P4 +project-secure-coding-practices,C04DZ254HFG,https://OWASP.slack.com/archives/C04DZ254HFG +project-go-scp,C04FG14MN5B,https://OWASP.slack.com/archives/C04FG14MN5B +project-ai-community,C04FV0D1GES,https://OWASP.slack.com/archives/C04FV0D1GES +project-devsecops-verification-standard,C04HD8ES72M,https://OWASP.slack.com/archives/C04HD8ES72M +project-mlsec-top-10,C04PESBUWRZ,https://OWASP.slack.com/archives/C04PESBUWRZ +project-developer-guide,C04QN6CMNAC,https://OWASP.slack.com/archives/C04QN6CMNAC +project-vulnerability-maturity-sig,C04QWA7R3C7,https://OWASP.slack.com/archives/C04QWA7R3C7 +project-blt-flutter-github,C04SCC5Q3RT,https://OWASP.slack.com/archives/C04SCC5Q3RT +project-committee-github,C0506NPJ2EM,https://OWASP.slack.com/archives/C0506NPJ2EM +project-asvs-nuclei,C052939BZ43,https://OWASP.slack.com/archives/C052939BZ43 +project-blt-codemagic,C052AAELH3P,https://OWASP.slack.com/archives/C052AAELH3P +project-new-projects,C052TF4AA84,https://OWASP.slack.com/archives/C052TF4AA84 +project-raider,C053YNZNEFP,https://OWASP.slack.com/archives/C053YNZNEFP +project-api-top10,C0558AF1QQM,https://OWASP.slack.com/archives/C0558AF1QQM +project-top10-for-llm,C05956H7R8R,https://OWASP.slack.com/archives/C05956H7R8R +project-osib,C05DPB4M1Q8,https://OWASP.slack.com/archives/C05DPB4M1Q8 +project-blt-prs,C05FBSPALLS,https://OWASP.slack.com/archives/C05FBSPALLS +project-nightingale,C05JPRM5GP8,https://OWASP.slack.com/archives/C05JPRM5GP8 +project-sweeper,C0607RP8MS8,https://OWASP.slack.com/archives/C0607RP8MS8 +project-securecodebox,C062TQANH3N,https://OWASP.slack.com/archives/C062TQANH3N +project-modsecurity,C069PCXSW12,https://OWASP.slack.com/archives/C069PCXSW12 +project-blockchain-appsec-standard,C06A53BF0QY,https://OWASP.slack.com/archives/C06A53BF0QY +project-security-c4po,C06ECA5U8SY,https://OWASP.slack.com/archives/C06ECA5U8SY +project-common-lifecycle-enumeration,C06GUKY03NC,https://OWASP.slack.com/archives/C06GUKY03NC +project-pscf,C06HQQF04CU,https://OWASP.slack.com/archives/C06HQQF04CU +project-sdrf,C06J07ZG7DE,https://OWASP.slack.com/archives/C06J07ZG7DE +project-llmvs,C06MDJG0KBK,https://OWASP.slack.com/archives/C06MDJG0KBK +project-blt-lettuce,C06R1H90JKV,https://OWASP.slack.com/archives/C06R1H90JKV +project-blt-lettuce-deploys,C06RBJ779CH,https://OWASP.slack.com/archives/C06RBJ779CH +project-blt-bacon,C06RNAENB4P,https://OWASP.slack.com/archives/C06RNAENB4P +project-flop-10,C072N37N82Z,https://OWASP.slack.com/archives/C072N37N82Z +project-ai-masteraisecurity,C077YSV1D7C,https://OWASP.slack.com/archives/C077YSV1D7C +project-netryx,C07D6R13URM,https://OWASP.slack.com/archives/C07D6R13URM +project-ot-top-10,C07HDTYRA6R,https://OWASP.slack.com/archives/C07HDTYRA6R +project-nest,C07JLLG2GFQ,https://OWASP.slack.com/archives/C07JLLG2GFQ +project-top10-proactive-controls,C07KNHZAN1H,https://OWASP.slack.com/archives/C07KNHZAN1H +project-actions,C07PMR5RV1A,https://OWASP.slack.com/archives/C07PMR5RV1A +project-aibom-community,C07UZUAJTL4,https://OWASP.slack.com/archives/C07UZUAJTL4 +project-scstg,C083UNMMVMH,https://OWASP.slack.com/archives/C083UNMMVMH diff --git a/website/management/commands/import_slack_channels.py b/website/management/commands/import_slack_channels.py new file mode 100644 index 0000000000..817f69abad --- /dev/null +++ b/website/management/commands/import_slack_channels.py @@ -0,0 +1,66 @@ +import csv +import os + +from django.core.management.base import BaseCommand + +from website.models import Project + + +class Command(BaseCommand): + help = "Import slack channels from CSV file and associate them with projects" + + def add_arguments(self, parser): + parser.add_argument( + "--csv-file", + type=str, + default="project_channels.csv", + help="Path to the CSV file containing the slack channel data", + ) + + def handle(self, *args, **kwargs): + csv_file_path = kwargs["csv_file"] + + self.stdout.write(f"Importing slack channels from CSV file: {csv_file_path}") + + if not os.path.exists(csv_file_path): + self.stdout.write(self.style.ERROR(f"CSV file not found: {csv_file_path}")) + return + + updated_count = 0 + + with open(csv_file_path, "r") as file: + reader = csv.DictReader(file) + for row in reader: + slack_channel = row.get("slack_channel", "").strip() + slack_id = row.get("slack_id", "").strip() + slack_url = row.get("slack_url", "").strip() + + if not slack_channel or not slack_channel.startswith("project-"): + continue + + project_name = slack_channel.replace("project-", "").replace("-", " ").title() + + project = Project.objects.filter(name__iexact=project_name).first() + + if project: + updated = False + if project.slack_channel != slack_channel: + project.slack_channel = slack_channel + updated = True + + if project.slack_id != slack_id: + project.slack_id = slack_id + updated = True + + if project.slack != slack_url: + project.slack = slack_url + updated = True + + if updated: + project.save() + updated_count += 1 + self.stdout.write(f"Updated project: {project_name}") + else: + self.stdout.write(self.style.WARNING(f"No project found with name: {project_name}")) + + self.stdout.write(self.style.SUCCESS(f"Successfully processed CSV. Updated {updated_count} projects.")) diff --git a/website/migrations/0247_add_slack_fields_to_project.py b/website/migrations/0247_add_slack_fields_to_project.py new file mode 100644 index 0000000000..7d2ffaed14 --- /dev/null +++ b/website/migrations/0247_add_slack_fields_to_project.py @@ -0,0 +1,27 @@ +# Generated by Django 5.1.8 on 2025-10-05 13:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("website", "0246_add_user_progress_models"), + ] + + operations = [ + migrations.AddField( + model_name="project", + name="slack", + field=models.URLField(blank=True, null=True), + ), + migrations.AddField( + model_name="project", + name="slack_channel", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name="project", + name="slack_id", + field=models.CharField(blank=True, max_length=255, null=True, unique=True), + ), + ] diff --git a/website/models.py b/website/models.py index 730a8eb0ae..b701529f28 100644 --- a/website/models.py +++ b/website/models.py @@ -1116,6 +1116,9 @@ class Project(models.Model): 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) + slack = models.URLField(null=True, blank=True) + slack_channel = models.CharField(max_length=255, blank=True, null=True) + slack_id = models.CharField(max_length=255, unique=True, blank=True, null=True) facebook = models.URLField(null=True, blank=True) logo = models.ImageField(upload_to="project_logos", null=True, blank=True) created = models.DateTimeField(auto_now_add=True) # Standardized field name @@ -1555,7 +1558,7 @@ class Meta: 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} on {self.date} [{self.granularity}]" class SlackBotActivity(models.Model): @@ -2327,7 +2330,7 @@ class Meta: unique_together = ("hackathon", "organization") def __str__(self): - return f"{self.organization.name} - {self.get_sponsor_level_display()} " f"sponsor for {self.hackathon.name}" + return f"{self.organization.name} - {self.get_sponsor_level_display()} sponsor for {self.hackathon.name}" class HackathonPrize(models.Model): diff --git a/website/templates/projects/project_detail.html b/website/templates/projects/project_detail.html index d7e0c8ad07..adc874f991 100644 --- a/website/templates/projects/project_detail.html +++ b/website/templates/projects/project_detail.html @@ -125,6 +125,17 @@