許多騎行團體都有定期騎行活動 - 週一早上的通勤車隊、每月第一個週六的冒險騎行,或週三晚上的社交騎行。到目前為止,組織者必須手動創建每個活動。借助我們新的週期性派對功能,您可以一次設置時間表,讓 Party Onbici 自動生成實例。
一次性活動的問題
社區騎行團體通常有可預測的時間表:
- “每週二和週四早上 6:30”
- “每月第一個週日”
- “隔週週六早上”
手動創建這些活動既繁瑣又容易出錯,而且難以在各次活動中保持一致的路線和設置。
介紹 iCalendar RRULE
我們基於 iCalendar RRULE 標準(RFC 5545)構建了週期性活動系統。這是 Google 日曆、Apple 日曆和 Outlook 使用的相同格式 - 確保與更廣泛的日曆生態系統兼容。
週期性模式示例
1
2
3
4
5
6
7
8
9
10
11
| RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR
→ 每週一、三、五
RRULE:FREQ=MONTHLY;BYDAY=1SA
→ 每月第一個週六
RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=SU
→ 隔週週日
RRULE:FREQ=DAILY;COUNT=10
→ 每天,共 10 次
|
數據模型
RecurringParty 作為生成 Party 實例的模板:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| class RecurringParty(models.Model):
name = models.CharField(max_length=255)
# 時間表配置
start_date = models.DateField()
end_date = models.DateField(null=True, blank=True)
max_occurrences = models.PositiveIntegerField(null=True, blank=True)
recurrence = RecurrenceField() # RRULE 模式
# 模板字段(複製到每個實例)
departure_time = models.TimeField()
arrival_time = models.TimeField()
origin = PointField()
destination = PointField()
route = models.JSONField()
difficulty = models.CharField(choices=DIFFICULTY_CHOICES)
# ... 其他派對屬性
is_active = models.BooleanField(default=True)
|
每個生成的 Party 實例都連接回其父級:
1
2
3
4
5
6
7
8
9
10
11
12
| class Party(models.Model):
# 現有字段...
# 週期性派對支持
scheduled_date = models.DateField(null=True, blank=True)
recurring_parent = models.ForeignKey(
RecurringParty,
on_delete=models.SET_NULL,
null=True,
related_name="instances"
)
is_cancelled = models.BooleanField(default=False)
|
實例生成
當創建週期性派對或時間表更改時,我們為即將到來的時期生成實例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| def generate_instances(self, lookahead_days: int = 30) -> list[Party]:
"""為即將發生的事件生成派對實例。"""
occurrences = self.recurrence.between(
datetime.now().date(),
datetime.now().date() + timedelta(days=lookahead_days),
)
created = []
for date in occurrences:
# 如果實例已存在則跳過
if self.instances.filter(scheduled_date=date).exists():
continue
# 從模板創建新的派對實例
party = Party.objects.create(
name=f"{self.name} - {date.strftime('%B %d')}",
scheduled_date=date,
recurring_parent=self,
departure_time=self.departure_time,
arrival_time=self.arrival_time,
origin=self.origin,
destination=self.destination,
route=self.route,
# ... 複製其他模板字段
)
created.append(party)
return created
|
使用 Celery Beat 自動生成
每日 Celery 任務保持實例最新:
1
2
3
4
5
6
7
8
9
| @shared_task
def generate_recurring_party_instances():
"""為所有活躍的週期性派對生成派對實例。"""
for recurring_party in RecurringParty.objects.filter(is_active=True):
created = recurring_party.generate_instances(lookahead_days=30)
if created:
logger.info(
f"為 {recurring_party.name} 生成了 {len(created)} 個實例"
)
|
Celery Beat 計劃在午夜運行:
1
2
3
4
5
6
| CELERY_BEAT_SCHEDULE = {
"generate-recurring-party-instances": {
"task": "party_onbici.apps.party.tasks.generate_recurring_party_instances",
"schedule": crontab(hour=0, minute=0), # 每天午夜
},
}
|
用戶界面
創建週期性派對
表單使用 django-recurrence 小部件構建 RRULE 模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| class RecurringPartyForm(forms.ModelForm):
recurrence = RecurrenceField(
label="週期性模式",
help_text="設置此派對應何時重複。",
required=True,
)
class Meta:
model = RecurringParty
fields = [
"name", "start_date", "end_date", "recurrence",
"departure_time", "arrival_time",
"origin", "destination", "description",
"difficulty", "gender", "privacy",
]
|
小部件為常見模式提供直觀的界面:
- 每天 / 每週 / 每月 / 每年
- 一週中的特定日子
- 序數模式(第一、第二、最後)
- 自定義間隔(每 2 週)
- 結束條件(到日期、N 次後、或永不)
管理實例
組織者可以查看所有生成的實例並:
- 編輯單個實例 - 更改一次活動的路線或時間
- 取消實例 - 將特定日期標記為已取消
- 重新生成實例 - 手動觸發接下來 30 天的生成
處理邊緣情況
時區感知
所有日期都存儲在組織者配置的時區中,並以查看者的本地時間顯示:
1
2
3
| # 使用時區感知日期生成實例
local_tz = get_current_timezone()
today = timezone.now().astimezone(local_tz).date()
|
假日和例外處理
用戶可以取消單個實例而不影響週期性模式:
1
2
3
| party = recurring_party.instances.get(scheduled_date=holiday_date)
party.is_cancelled = True
party.save()
|
已取消的實例仍會在列表中顯示,帶有視覺指示器,防止混淆。
孤立實例
如果刪除週期性派對,生成的實例可以選擇保留:
1
2
3
4
5
6
7
8
9
10
11
| def form_valid(self, form):
delete_instances = request.POST.get("delete_instances") == "true"
if delete_instances:
# 軟刪除未來實例
recurring_party.instances.filter(
scheduled_date__gte=date.today(),
deleted_at__isnull=True,
).update(deleted_at=timezone.now())
recurring_party.delete()
|
用戶體驗
創建每週騎行只需幾個步驟:
- 前往 儀表板 → 週期性派對 → 創建
- 設置您的派對詳情(路線、時間、難度)
- 配置週期性模式(例如,“每週六早上 8 點”)
- 設置開始日期和可選的結束日期
- 保存 - 實例會自動生成!
系統提前 30 天創建派對實例,Celery 會隨著時間推移繼續生成新實例。
騎行社區的好處
- 設置後無需操心 - 不再需要每週創建活動
- 一致性 - 相同的路線、相同的時間、相同的設置
- 靈活性 - 編輯或取消單個活動
- 發現 - 騎行者可以輕鬆找到定期的團體騎行
- 日曆整合 - 在您的日曆應用中訂閱週期性活動
準備好設置您的定期騎行了嗎?創建週期性派對,讓我們來處理排程!