Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions backend/apps/nest/migrations/0008_reminder_reminderschedule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Generated by Django 5.2.4 on 2025-09-02 13:29

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("nest", "0007_alter_googleaccountauthorization_options_and_more"),
("slack", "0018_conversation_sync_messages"),
]

operations = [
migrations.CreateModel(
name="Reminder",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
(
"channel_id",
models.CharField(default="", max_length=15, verbose_name="Channel ID"),
),
("message", models.TextField(verbose_name="Reminder Message")),
(
"member",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="reminders",
to="slack.member",
verbose_name="Slack Member",
),
),
],
options={
"verbose_name": "Nest Reminder",
"verbose_name_plural": "Nest Reminders",
"db_table": "nest_reminders",
},
),
migrations.CreateModel(
name="ReminderSchedule",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("scheduled_time", models.DateTimeField(verbose_name="Scheduled Time")),
(
"recurrence",
models.CharField(
choices=[
("once", "Once"),
("daily", "Daily"),
("weekly", "Weekly"),
("monthly", "Monthly"),
],
default="once",
max_length=50,
verbose_name="Recurrence",
),
),
(
"reminder",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="schedules",
to="nest.reminder",
verbose_name="Nest Reminder",
),
),
],
options={
"verbose_name": "Nest Reminder Schedule",
"verbose_name_plural": "Nest Reminder Schedules",
"db_table": "nest_reminder_schedules",
},
),
]
2 changes: 2 additions & 0 deletions backend/apps/nest/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from .api_key import ApiKey
from .badge import Badge
from .google_account_authorization import GoogleAccountAuthorization
from .reminder import Reminder
from .reminder_schedule import ReminderSchedule
from .user import User
from .user_badge import UserBadge
26 changes: 26 additions & 0 deletions backend/apps/nest/models/reminder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Nest Reminder model."""

from django.db import models


class Reminder(models.Model):
"""Model representing a reminder."""

class Meta:
db_table = "nest_reminders"
verbose_name = "Nest Reminder"
verbose_name_plural = "Nest Reminders"

member = models.ForeignKey(
"slack.Member",
verbose_name="Slack Member",
on_delete=models.SET_NULL,
related_name="reminders",
null=True,
)
channel_id = models.CharField(verbose_name="Channel ID", max_length=15, default="")
message = models.TextField(verbose_name="Reminder Message")

def __str__(self) -> str:
"""Reminder human readable representation."""
return f"Reminder for {self.member} in channel: {self.channel_id}: {self.message}"
36 changes: 36 additions & 0 deletions backend/apps/nest/models/reminder_schedule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Nest Reminder Schedule model."""

from django.db import models


class ReminderSchedule(models.Model):
"""Model representing a reminder schedule."""

class Meta:
db_table = "nest_reminder_schedules"
verbose_name = "Nest Reminder Schedule"
verbose_name_plural = "Nest Reminder Schedules"

class Recurrence(models.TextChoices):
ONCE = "once", "Once"
DAILY = "daily", "Daily"
WEEKLY = "weekly", "Weekly"
MONTHLY = "monthly", "Monthly"

reminder = models.ForeignKey(
"nest.Reminder",
verbose_name="Nest Reminder",
on_delete=models.CASCADE,
related_name="schedules",
)
scheduled_time = models.DateTimeField(verbose_name="Scheduled Time")
recurrence = models.CharField(
verbose_name="Recurrence",
max_length=50,
choices=Recurrence.choices,
default=Recurrence.ONCE,
)

def __str__(self) -> str:
"""Reminder Schedule human readable representation."""
return f"Schedule for {self.reminder} at {self.scheduled_time} ({self.recurrence})"
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Generated by Django 5.2.4 on 2025-08-26 00:34

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("owasp", "0048_entitymember"),
("slack", "0018_conversation_sync_messages"),
]

operations = [
migrations.AddField(
model_name="event",
name="calendar_id",
field=models.CharField(
blank=True, default="", max_length=1024, verbose_name="Calendar ID"
),
),
migrations.AddField(
model_name="event",
name="channel_id",
field=models.CharField(default="", max_length=15, verbose_name="Channel ID"),
),
migrations.AddField(
model_name="event",
name="member",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="calendar_events",
to="slack.member",
verbose_name="Slack Member",
),
),
migrations.AddField(
model_name="event",
name="status",
field=models.CharField(
choices=[
("confirmed", "Confirmed"),
("tentative", "Tentative"),
("canceled", "Canceled"),
],
default="confirmed",
max_length=11,
verbose_name="Status",
),
),
migrations.AddField(
model_name="event",
name="type",
field=models.CharField(
choices=[("calendar", "Calendar"), ("existing", "Existing")],
default="existing",
max_length=11,
verbose_name="Type",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 5.2.4 on 2025-08-26 01:52

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("owasp", "0049_event_calendar_id_event_channel_id_event_member_and_more"),
("slack", "0018_conversation_sync_messages"),
]

operations = [
migrations.AlterField(
model_name="event",
name="status",
field=models.CharField(
choices=[
("confirmed", "Confirmed"),
("tentative", "Tentative"),
("cancelled", "Cancelled"),
],
default="confirmed",
max_length=11,
verbose_name="Status",
),
),
migrations.AddIndex(
model_name="event",
index=models.Index(fields=["type"], name="event_type_idx"),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Generated by Django 5.2.4 on 2025-08-28 16:19

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("owasp", "0050_alter_event_status_event_event_type_idx"),
]

operations = [
migrations.RemoveIndex(
model_name="event",
name="event_type_idx",
),
migrations.RemoveField(
model_name="event",
name="channel_id",
),
migrations.RemoveField(
model_name="event",
name="member",
),
migrations.RemoveField(
model_name="event",
name="type",
),
migrations.AlterField(
model_name="event",
name="category",
field=models.CharField(
choices=[
("appsec_days", "AppSec Days"),
("global", "Global"),
("other", "Other"),
("partner", "Partner"),
("community", "Community"),
],
default="other",
max_length=11,
verbose_name="Category",
),
),
migrations.AlterField(
model_name="event",
name="status",
field=models.CharField(
choices=[("confirmed", "Confirmed")],
default="confirmed",
max_length=11,
verbose_name="Status",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.2.4 on 2025-09-02 13:15

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("owasp", "0051_remove_event_event_type_idx_remove_event_channel_id_and_more"),
]

operations = [
migrations.RemoveField(
model_name="event",
name="calendar_id",
),
migrations.AddField(
model_name="event",
name="google_calendar_id",
field=models.CharField(
blank=True, default="", max_length=1024, verbose_name="Google Calendar ID"
),
),
]
Comment on lines +11 to +23
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Preserve data: use RenameField (then AlterField) instead of Remove/Add.

Removing then re-adding drops existing values in calendar_id. Prefer a rename to keep data, followed by an AlterField for the label.

-        migrations.RemoveField(
-            model_name="event",
-            name="calendar_id",
-        ),
-        migrations.AddField(
-            model_name="event",
-            name="google_calendar_id",
-            field=models.CharField(
-                blank=True, default="", max_length=1024, verbose_name="Google Calendar ID"
-            ),
-        ),
+        migrations.RenameField(
+            model_name="event",
+            old_name="calendar_id",
+            new_name="google_calendar_id",
+        ),
+        migrations.AlterField(
+            model_name="event",
+            name="google_calendar_id",
+            field=models.CharField(
+                blank=True, default="", max_length=1024, verbose_name="Google Calendar Event ID"
+            ),
+        ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
operations = [
migrations.RemoveField(
model_name="event",
name="calendar_id",
),
migrations.AddField(
model_name="event",
name="google_calendar_id",
field=models.CharField(
blank=True, default="", max_length=1024, verbose_name="Google Calendar ID"
),
),
]
operations = [
migrations.RenameField(
model_name="event",
old_name="calendar_id",
new_name="google_calendar_id",
),
migrations.AlterField(
model_name="event",
name="google_calendar_id",
field=models.CharField(
blank=True,
default="",
max_length=1024,
verbose_name="Google Calendar Event ID",
),
),
]
🤖 Prompt for AI Agents
In
backend/apps/owasp/migrations/0052_remove_event_calendar_id_event_google_calendar_id.py
around lines 11 to 23, the migration removes the existing calendar_id field and
re-adds google_calendar_id which drops existing data; instead replace the
RemoveField+AddField with a migrations.RenameField from "calendar_id" to
"google_calendar_id" to preserve values, then add a separate
migrations.AlterField to adjust blank/default/verbose_name as needed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old migrations are not migrated yet

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@ahmedxgouda, thanks for the clarification! If the old migrations haven't been applied yet, you have a few cleaner options:

Option 1: Modify the original migration
Since migration 0049 hasn't been applied, you could edit it directly to use google_calendar_id instead of calendar_id, then delete migration 0052 entirely.

Option 2: Squash the migrations
Use Django's squashmigrations command to combine 0049-0052 into a single migration:

If you choose to keep the current approach, you're right that data preservation isn't a concern since no data exists yet. The RemoveField+AddField pattern will work fine in this case.


🏁 Script executed:

#!/bin/bash
# Show the current migration files to understand the sequence
find backend/apps/owasp/migrations/ -name "004*.py" -o -name "005*.py" | sort

Length of output: 1171


Perfect! Since migrations 0049-0052 haven't been applied yet, here's the cleanest approach:

Recommended: Squash the migrations

This will create a single, clean migration that:

  • Adds only the final fields you actually want
  • Avoids the back-and-forth of adding/removing fields
  • Results in cleaner migration history

Alternative: Keep current approach
Since no data exists yet, your current RemoveField+AddField approach in 0052 is perfectly safe. The "data loss" concern only applies when there's actual data to lose.

Quick verification - check migration state:

If none show [X] (applied), then you're good to proceed with either approach!


🏁 Script executed:

#!/bin/bash
# Show what squashing would do (dry run)
cd backend
python manage.py squashmigrations owasp 0049 0052 --name=update_event_model_calendar --dry-run

Length of output: 334


🏁 Script executed:

#!/bin/bash
# Check if any of these migrations have been applied
cd backend
python manage.py showmigrations owasp | grep -E "(0049|0050|0051|0052)"

Length of output: 311


@ahmedxgouda, the sandbox environment doesn't have the Django dependencies installed, but I can still provide guidance based on your confirmation that the migrations haven't been applied yet.

Since migrations 0049-0052 haven't run, you have these options:

Option 1: Keep your current approach (simplest)
Your current migration 0052 with RemoveField+AddField is perfectly safe since there's no data to lose. This approach will work fine.

Option 2: Squash migrations (cleanest for production)
In your local environment, you can run:

cd backend
python manage.py squashmigrations owasp 0049 0052 --name=update_event_model_calendar

This creates a single migration replacing 0049-0052, avoiding the field churn.

Option 3: Manual edit (if you prefer)
Edit migration 0049 directly to use google_calendar_id instead of calendar_id, then delete migration 0052.

Recommendation: Since you've already written migration 0052 and confirmed no data exists, go ahead with your current approach. It's functionally equivalent to the alternatives and avoids additional work.

The data preservation concern in my original comment only applies when migrations have been applied and contain real data.

17 changes: 17 additions & 0 deletions backend/apps/owasp/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,34 @@ class Category(models.TextChoices):
GLOBAL = "global", "Global"
OTHER = "other", "Other"
PARTNER = "partner", "Partner"
COMMUNITY = "community", "Community"

class Status(models.TextChoices):
"""Event status."""

CONFIRMED = "confirmed", "Confirmed"

category = models.CharField(
verbose_name="Category",
max_length=11,
choices=Category.choices,
default=Category.OTHER,
)
# Google Calendar event entity ID
google_calendar_id = models.CharField(
verbose_name="Google Calendar ID", max_length=1024, blank=True, default=""
)
name = models.CharField(verbose_name="Name", max_length=100)
start_date = models.DateField(verbose_name="Start Date")
end_date = models.DateField(verbose_name="End Date", null=True, blank=True)
description = models.TextField(verbose_name="Description", default="", blank=True)
key = models.CharField(verbose_name="Key", max_length=100, unique=True)
status = models.CharField(
verbose_name="Status",
max_length=11,
choices=Status.choices,
default=Status.CONFIRMED,
)
summary = models.TextField(verbose_name="Summary", blank=True, default="")
suggested_location = models.CharField(
verbose_name="Suggested Location", max_length=255, blank=True, default=""
Expand All @@ -77,6 +93,7 @@ def upcoming_events():
)
.exclude(
Q(name__exact="") | Q(url__exact=""),
category=Event.Category.COMMUNITY,
)
.order_by(
"start_date",
Expand Down
Loading