-
-
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 24 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 |
---|---|---|
|
@@ -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,21 @@ | ||
"""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", | ||
"category_description", | ||
"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 datetime, 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 = datetime.now(timezone.utc).date() | ||
|
||
base_query = Event.objects.exclude(start_date__isnull=True).filter(start_date__gte=today) | ||
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. start_date should not be nullable |
||
|
||
return base_query.order_by("start_date") |
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,69 @@ | ||||||||||||||||
"""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", "") | ||||||||||||||||
category_description = category.get("description", "") | ||||||||||||||||
|
||||||||||||||||
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), | ||||||||||||||||
"category_description": category_description, | ||||||||||||||||
"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,83 @@ | ||
# Generated by Django 5.1.5 on 2025-02-20 16:06 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
("owasp", "0014_project_custom_tags"), | ||
] | ||
|
||
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="category_description", | ||
field=models.TextField(blank=True, default="", verbose_name="Category Description"), | ||
), | ||
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(blank=True, default="2025-01-01", verbose_name="Start Date"), | ||
), | ||
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,46 @@ | |
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) | ||
category = models.CharField( | ||
verbose_name="Category", | ||
max_length=20, | ||
choices=EventCategory.choices, | ||
default=EventCategory.OTHER, | ||
) | ||
|
||
owasp_repository = models.ForeignKey( | ||
"github.Repository", on_delete=models.SET_NULL, blank=True, null=True | ||
category_description = models.TextField( | ||
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. You can remove this field. The description should belong to category entity, not to the event as it's always tied to the category. |
||
verbose_name="Category Description", default="", blank=True | ||
) | ||
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", default="2025-01-01", blank=True) | ||
abhayymishraa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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.
Use
timezone.now()
fromdjango.utils