-
-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improved NestBot /events command #657
Changes from 29 commits
825baa1
797e7f8
dd2b4a5
0c601d1
eee4b37
823f367
3962eb1
fc95c4d
49e6dd0
8148738
0906959
79cb0e3
3a73341
71bef1d
4558d4d
17a502d
27ea9c5
fafcf81
a3f7c90
7482cb4
e951eb0
98f6659
91271d1
52c180a
b5d0390
9b37d90
216a6da
337173c
23f4cc1
ef3929f
68d4d84
29cd4a2
ba75fb8
e6d7be4
e6c3e1c
5aa4f51
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -88,7 +88,11 @@ owasp-scrape-committees: | |||||||
|
||||||||
owasp-scrape-projects: | ||||||||
@echo "Scraping OWASP site projects data" | ||||||||
@CMD="python manage.py owasp_scrape_projects" $(MAKE) exec-backend-command | ||||||||
@CMD="poetry run python manage.py owasp_scrape_projects" $(MAKE) exec-backend-command | ||||||||
|
||||||||
owasp-update-events: | ||||||||
@echo "Getting OWASP events data" | ||||||||
@CMD="poetry run python manage.py owasp_update_events" $(MAKE) exec-backend-command | ||||||||
|
||||||||
purge-data: | ||||||||
@CMD="python manage.py purge_data" $(MAKE) exec-backend-command | ||||||||
|
@@ -97,7 +101,7 @@ recreate-schema: | |||||||
@CMD="psql -U nest_user_dev -d nest_db_dev -c \ | ||||||||
'DROP SCHEMA public CASCADE; CREATE SCHEMA public; GRANT ALL ON SCHEMA public TO nest_user_dev'" \ | ||||||||
$(MAKE) exec-db-command 2>/dev/null | ||||||||
$(MAKE) migrate | ||||||||
$(MAKE) migrateNestBot goes directly to the DB | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential issue with 'migrateNestBot goes directly to the DB'. Check whether "migrateNestBot" is a valid target or a leftover comment, as this line might cause a Makefile error. - $(MAKE) migrateNestBot goes directly to the DB
+ # Possibly intended:
+ $(MAKE) migrate 📝 Committable suggestion
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This suggestion is actually useful There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @arkid15r I'm glad you found the suggestion helpful. Would you like me to implement this change for you? The current command |
||||||||
|
||||||||
setup: | ||||||||
@CMD="python manage.py createsuperuser" $(MAKE) exec-backend-command-it | ||||||||
|
@@ -123,4 +127,5 @@ update-data: \ | |||||||
owasp-scrape-committees \ | ||||||||
owasp-scrape-projects \ | ||||||||
github-update-project-related-repositories \ | ||||||||
owasp-aggregate-projects | ||||||||
owasp-aggregate-projects \ | ||||||||
owasp-update-events |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,8 +15,6 @@ class Meta: | |
"name", | ||
"description", | ||
"url", | ||
"created_at", | ||
"updated_at", | ||
) | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
"""OWASP event GraphQL node.""" | ||
|
||
from apps.common.graphql.nodes import BaseNode | ||
from apps.owasp.models.event import Event | ||
|
||
|
||
class EventNode(BaseNode): | ||
"""Event node.""" | ||
|
||
class Meta: | ||
model = Event | ||
fields = ( | ||
"category", | ||
"end_date", | ||
"description", | ||
"key", | ||
"name", | ||
"start_date", | ||
"url", | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
"""OWASP event GraphQL queries.""" | ||
|
||
from datetime import timezone | ||
|
||
import graphene | ||
|
||
from apps.common.graphql.queries import BaseQuery | ||
from apps.owasp.graphql.nodes.event import EventNode | ||
from apps.owasp.models.event import Event | ||
|
||
|
||
class EventQuery(BaseQuery): | ||
"""Event queries.""" | ||
|
||
events = graphene.List(EventNode) | ||
|
||
def resolve_events(root, info): | ||
"""Resolve all events.""" | ||
today = timezone.now() | ||
|
||
base_query = Event.objects.filter(start_date__gte=today) | ||
|
||
return base_query.order_by("start_date") |
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,67 @@ | ||||||||||||||||
"""A command to update OWASP events.""" | ||||||||||||||||
|
||||||||||||||||
import yaml | ||||||||||||||||
from django.core.management.base import BaseCommand | ||||||||||||||||
from django.utils.text import slugify | ||||||||||||||||
|
||||||||||||||||
from apps.github.utils import get_repository_file_content, normalize_url | ||||||||||||||||
from apps.owasp.models.event import Event, EventCategory | ||||||||||||||||
from apps.owasp.utils import parse_event_dates | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
class Command(BaseCommand): | ||||||||||||||||
help = "Import events from the provided YAML file" | ||||||||||||||||
|
||||||||||||||||
def handle(self, *args, **kwargs): | ||||||||||||||||
url = "https://raw.githubusercontent.com/OWASP/owasp.github.io/main/_data/events.yml" | ||||||||||||||||
yaml_content = get_repository_file_content(url) | ||||||||||||||||
data = yaml.safe_load(yaml_content) | ||||||||||||||||
events = [] | ||||||||||||||||
|
||||||||||||||||
for category in data: | ||||||||||||||||
category_name = category.get("category", "") | ||||||||||||||||
|
||||||||||||||||
for event_data in category["events"]: | ||||||||||||||||
event_name_slug = slugify(event_data.get("name", "")) | ||||||||||||||||
key = event_name_slug | ||||||||||||||||
end_date = parse_event_dates( | ||||||||||||||||
event_data.get("dates", ""), event_data.get("start-date") | ||||||||||||||||
) | ||||||||||||||||
|
||||||||||||||||
fields = { | ||||||||||||||||
"category": get_event_category(category_name), | ||||||||||||||||
"end_date": end_date, | ||||||||||||||||
"key": key, | ||||||||||||||||
"name": event_data.get("name", ""), | ||||||||||||||||
"description": event_data.get("optional-text", ""), | ||||||||||||||||
"start_date": event_data.get("start-date", None), | ||||||||||||||||
"url": normalize_url(event_data.get("url", "")) or "", | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❓ Verification inconclusiveEnsure start_date is properly parsed. The "key": key,
"name": event_data.get("name", ""),
"description": event_data.get("optional-text", ""),
- "start_date": event_data.get("start-date", None),
+ "start_date": parser.parse(event_data.get("start-date")).date() if event_data.get("start-date") else None,
"url": normalize_url(event_data.get("url", "")) or "", 🏁 Script executed: #!/bin/bash
# Check the Event model definition to confirm start_date field type
rg "start_date" -A 1 -B 1 "apps/owasp/models/event.py" Length of output: 119 Attention: Confirm that the Event model accepts a date object The change correctly converts the YAML string into a date object using
📝 Committable suggestion
Suggested change
|
||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
try: | ||||||||||||||||
event = Event.objects.get(key=key) | ||||||||||||||||
# Update existing event | ||||||||||||||||
for key, value in fields.items(): | ||||||||||||||||
setattr(event, key, value) | ||||||||||||||||
events.append(event) | ||||||||||||||||
except Event.DoesNotExist: | ||||||||||||||||
# Create new event | ||||||||||||||||
event = Event(**fields) | ||||||||||||||||
events.append(event) | ||||||||||||||||
|
||||||||||||||||
if events: | ||||||||||||||||
self.stdout.write(f"Saving {len(events)} events...") | ||||||||||||||||
Event.bulk_save(events, fields) | ||||||||||||||||
self.stdout.write(self.style.SUCCESS("Successfully saved events")) | ||||||||||||||||
else: | ||||||||||||||||
self.stdout.write(self.style.WARNING("No events to save")) | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
def get_event_category(category_name): | ||||||||||||||||
"""Get event category.""" | ||||||||||||||||
category_map = { | ||||||||||||||||
"Global": EventCategory.GLOBAL, | ||||||||||||||||
"AppSec Days": EventCategory.APPSEC_DAYS, | ||||||||||||||||
"Partner": EventCategory.PARTNER, | ||||||||||||||||
} | ||||||||||||||||
return category_map.get(category_name, EventCategory.OTHER) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
# Generated by Django 5.1.5 on 2025-02-28 07:53 | ||
|
||
import datetime | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
("owasp", "0015_snapshot"), | ||
] | ||
|
||
operations = [ | ||
migrations.RemoveField( | ||
model_name="event", | ||
name="created_at", | ||
), | ||
migrations.RemoveField( | ||
model_name="event", | ||
name="has_active_repositories", | ||
), | ||
migrations.RemoveField( | ||
model_name="event", | ||
name="is_active", | ||
), | ||
migrations.RemoveField( | ||
model_name="event", | ||
name="level", | ||
), | ||
migrations.RemoveField( | ||
model_name="event", | ||
name="owasp_repository", | ||
), | ||
migrations.RemoveField( | ||
model_name="event", | ||
name="summary", | ||
), | ||
migrations.RemoveField( | ||
model_name="event", | ||
name="tags", | ||
), | ||
migrations.RemoveField( | ||
model_name="event", | ||
name="topics", | ||
), | ||
migrations.RemoveField( | ||
model_name="event", | ||
name="updated_at", | ||
), | ||
migrations.AddField( | ||
model_name="event", | ||
name="category", | ||
field=models.CharField( | ||
choices=[ | ||
("global", "Global"), | ||
("appsec_days", "AppSec Days"), | ||
("partner", "Partner"), | ||
("other", "Other"), | ||
], | ||
default="other", | ||
max_length=20, | ||
verbose_name="Category", | ||
), | ||
), | ||
migrations.AddField( | ||
model_name="event", | ||
name="end_date", | ||
field=models.DateField(blank=True, null=True, verbose_name="End Date"), | ||
), | ||
migrations.AddField( | ||
model_name="event", | ||
name="start_date", | ||
field=models.DateField( | ||
default=datetime.datetime( | ||
2025, 2, 28, 7, 53, 17, 155842, tzinfo=datetime.timezone.utc | ||
), | ||
verbose_name="Start Date", | ||
), | ||
preserve_default=False, | ||
), | ||
migrations.AlterField( | ||
model_name="event", | ||
name="description", | ||
field=models.TextField(blank=True, default="", verbose_name="Description"), | ||
), | ||
] |
arkid15r marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,56 +3,43 @@ | |
from django.db import models | ||
|
||
from apps.common.models import BulkSaveModel, TimestampedModel | ||
from apps.owasp.models.common import RepositoryBasedEntityModel | ||
|
||
|
||
class Event(BulkSaveModel, RepositoryBasedEntityModel, TimestampedModel): | ||
class EventCategory(models.TextChoices): | ||
"""Event category choices.""" | ||
|
||
GLOBAL = "global", "Global" | ||
APPSEC_DAYS = "appsec_days", "AppSec Days" | ||
PARTNER = "partner", "Partner" | ||
OTHER = "other", "Other" | ||
|
||
|
||
class Event(BulkSaveModel, TimestampedModel): | ||
"""Event model.""" | ||
|
||
class Meta: | ||
db_table = "owasp_events" | ||
verbose_name_plural = "Events" | ||
|
||
level = models.CharField(verbose_name="Level", max_length=5, default="", blank=True) | ||
url = models.URLField(verbose_name="URL", default="", blank=True) | ||
|
||
owasp_repository = models.ForeignKey( | ||
"github.Repository", on_delete=models.SET_NULL, blank=True, null=True | ||
category = models.CharField( | ||
verbose_name="Category", | ||
max_length=20, | ||
choices=EventCategory.choices, | ||
default=EventCategory.OTHER, | ||
) | ||
|
||
end_date = models.DateField(verbose_name="End Date", null=True, blank=True) | ||
key = models.CharField(verbose_name="Key", max_length=100, unique=True) | ||
name = models.CharField(verbose_name="Name", max_length=100) | ||
description = models.TextField(verbose_name="Description", default="", blank=True) | ||
start_date = models.DateField(verbose_name="Start Date") | ||
url = models.URLField(verbose_name="URL", default="", blank=True) | ||
|
||
def __str__(self): | ||
"""Event human readable representation.""" | ||
return f"{self.name or self.key}" | ||
|
||
def from_github(self, repository): | ||
"""Update instance based on GitHub repository data.""" | ||
field_mapping = { | ||
"description": "pitch", | ||
"level": "level", | ||
"name": "title", | ||
"tags": "tags", | ||
} | ||
RepositoryBasedEntityModel.from_github(self, field_mapping, repository) | ||
|
||
# FKs. | ||
self.owasp_repository = repository | ||
|
||
@staticmethod | ||
def bulk_save(events, fields=None): | ||
"""Bulk save events.""" | ||
BulkSaveModel.bulk_save(Event, events, fields=fields) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe you should keep this method but just update its logic based on the new requirements. |
||
@staticmethod | ||
def update_data(gh_repository, repository, save=True): | ||
"""Update event data.""" | ||
key = gh_repository.name.lower() | ||
try: | ||
event = Event.objects.get(key=key) | ||
except Event.DoesNotExist: | ||
event = Event(key=key) | ||
|
||
event.from_github(repository) | ||
if save: | ||
event.save() | ||
|
||
return event |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why changing this and adding
poetry
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i can fix that