Nhiều nhóm đạp xe có các chuyến đi thường xuyên - đội đi làm sáng thứ Hai, chuyến phiêu lưu thứ Bảy đầu tháng, hoặc vòng giao lưu tối thứ Tư. Cho đến nay, người tổ chức phải tạo thủ công từng sự kiện. Với tính năng Bữa tiệc Định kỳ mới, bạn có thể thiết lập lịch một lần và để Party Onbici tự động tạo các phiên bản.
Vấn đề với các sự kiện một lần
Các nhóm đạp xe cộng đồng thường có lịch trình có thể dự đoán được:
- “Mỗi thứ Ba và thứ Năm lúc 6:30 sáng”
- “Chủ nhật đầu tiên mỗi tháng”
- “Cách tuần vào sáng thứ Bảy”
Việc tạo thủ công các sự kiện này rất tẻ nhạt, dễ xảy ra lỗi và khó duy trì các tuyến đường và cài đặt nhất quán qua các lần.
Giới thiệu iCalendar RRULE
Chúng tôi xây dựng hệ thống sự kiện định kỳ dựa trên tiêu chuẩn iCalendar RRULE (RFC 5545). Đây là cùng định dạng được sử dụng bởi Google Calendar, Apple Calendar và Outlook - đảm bảo khả năng tương thích với hệ sinh thái lịch rộng hơn.
Ví dụ về mẫu lặp lại
1
2
3
4
5
6
7
8
9
10
11
| RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR
→ Mỗi thứ Hai, thứ Tư và thứ Sáu
RRULE:FREQ=MONTHLY;BYDAY=1SA
→ Thứ Bảy đầu tiên mỗi tháng
RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=SU
→ Cách tuần vào Chủ nhật
RRULE:FREQ=DAILY;COUNT=10
→ Hàng ngày trong 10 lần
|
Mô hình dữ liệu
Một RecurringParty đóng vai trò như một mẫu tạo ra các phiên bản 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)
# Cấu hình lịch trình
start_date = models.DateField()
end_date = models.DateField(null=True, blank=True)
max_occurrences = models.PositiveIntegerField(null=True, blank=True)
recurrence = RecurrenceField() # mẫu RRULE
# Các trường mẫu (được sao chép vào mỗi phiên bản)
departure_time = models.TimeField()
arrival_time = models.TimeField()
origin = PointField()
destination = PointField()
route = models.JSONField()
difficulty = models.CharField(choices=DIFFICULTY_CHOICES)
# ... các thuộc tính bữa tiệc khác
is_active = models.BooleanField(default=True)
|
Mỗi phiên bản Party được tạo liên kết ngược về cha mẹ của nó:
1
2
3
4
5
6
7
8
9
10
11
12
| class Party(models.Model):
# Các trường hiện có...
# Hỗ trợ bữa tiệc định kỳ
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)
|
Tạo phiên bản
Khi một bữa tiệc định kỳ được tạo hoặc lịch trình thay đổi, chúng tôi tạo các phiên bản cho khoảng thời gian sắp tới:
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]:
"""Tạo các phiên bản bữa tiệc cho các lần xuất hiện sắp tới."""
occurrences = self.recurrence.between(
datetime.now().date(),
datetime.now().date() + timedelta(days=lookahead_days),
)
created = []
for date in occurrences:
# Bỏ qua nếu phiên bản đã tồn tại
if self.instances.filter(scheduled_date=date).exists():
continue
# Tạo phiên bản bữa tiệc mới từ mẫu
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,
# ... sao chép các trường mẫu khác
)
created.append(party)
return created
|
Tạo tự động với Celery Beat
Một tác vụ Celery hàng ngày giữ các phiên bản được cập nhật:
1
2
3
4
5
6
7
8
9
| @shared_task
def generate_recurring_party_instances():
"""Tạo các phiên bản bữa tiệc cho tất cả các bữa tiệc định kỳ đang hoạt động."""
for recurring_party in RecurringParty.objects.filter(is_active=True):
created = recurring_party.generate_instances(lookahead_days=30)
if created:
logger.info(
f"Đã tạo {len(created)} phiên bản cho {recurring_party.name}"
)
|
Lịch Celery Beat chạy điều này vào lúc nửa đêm:
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), # Hàng ngày lúc nửa đêm
},
}
|
Giao diện người dùng
Tạo bữa tiệc định kỳ
Biểu mẫu sử dụng widget django-recurrence để xây dựng các mẫu RRULE:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| class RecurringPartyForm(forms.ModelForm):
recurrence = RecurrenceField(
label="Mẫu lặp lại",
help_text="Thiết lập khi nào bữa tiệc này nên lặp lại.",
required=True,
)
class Meta:
model = RecurringParty
fields = [
"name", "start_date", "end_date", "recurrence",
"departure_time", "arrival_time",
"origin", "destination", "description",
"difficulty", "gender", "privacy",
]
|
Widget cung cấp giao diện trực quan cho các mẫu phổ biến:
- Hàng ngày / Hàng tuần / Hàng tháng / Hàng năm
- Các ngày cụ thể trong tuần
- Mẫu thứ tự (đầu tiên, thứ hai, cuối cùng)
- Khoảng cách tùy chỉnh (mỗi 2 tuần)
- Điều kiện kết thúc (đến ngày, sau N lần, hoặc không bao giờ)
Quản lý phiên bản
Người tổ chức có thể xem tất cả các phiên bản đã tạo và:
- Chỉnh sửa phiên bản riêng lẻ - Thay đổi tuyến đường hoặc thời gian cho một lần
- Hủy phiên bản - Đánh dấu các ngày cụ thể là đã hủy
- Tạo lại phiên bản - Kích hoạt thủ công việc tạo cho 30 ngày tiếp theo
Xử lý các trường hợp đặc biệt
Nhận thức múi giờ
Tất cả các ngày được lưu trữ trong múi giờ đã cấu hình của người tổ chức và hiển thị theo giờ địa phương của người xem:
1
2
3
| # Tạo phiên bản sử dụng ngày có nhận thức múi giờ
local_tz = get_current_timezone()
today = timezone.now().astimezone(local_tz).date()
|
Xử lý ngày lễ và ngoại lệ
Người dùng có thể hủy các phiên bản riêng lẻ mà không ảnh hưởng đến mẫu định kỳ:
1
2
3
| party = recurring_party.instances.get(scheduled_date=holiday_date)
party.is_cancelled = True
party.save()
|
Các phiên bản đã hủy vẫn xuất hiện trong danh sách với chỉ báo trực quan, ngăn ngừa nhầm lẫn.
Phiên bản mồ côi
Nếu một bữa tiệc định kỳ bị xóa, các phiên bản đã tạo có thể được giữ lại tùy chọn:
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:
# Xóa mềm các phiên bản trong tương lai
recurring_party.instances.filter(
scheduled_date__gte=date.today(),
deleted_at__isnull=True,
).update(deleted_at=timezone.now())
recurring_party.delete()
|
Trải nghiệm người dùng
Tạo một chuyến đi hàng tuần chỉ mất vài bước:
- Đi đến Bảng điều khiển → Bữa tiệc định kỳ → Tạo
- Thiết lập chi tiết bữa tiệc của bạn (tuyến đường, thời gian, độ khó)
- Cấu hình mẫu lặp lại (ví dụ: “Mỗi thứ Bảy lúc 8 giờ sáng”)
- Đặt ngày bắt đầu và ngày kết thúc tùy chọn
- Lưu - các phiên bản được tạo tự động!
Hệ thống tạo các phiên bản bữa tiệc trước 30 ngày, và Celery tiếp tục tạo mới khi thời gian trôi qua.
Lợi ích cho cộng đồng đạp xe
- Thiết lập và quên - Không còn phải tạo sự kiện hàng tuần
- Nhất quán - Cùng tuyến đường, cùng thời gian, cùng cài đặt
- Linh hoạt - Chỉnh sửa hoặc hủy các lần riêng lẻ
- Khám phá - Người đi xe có thể dễ dàng tìm thấy các chuyến đi nhóm thường xuyên
- Tích hợp lịch - Đăng ký các sự kiện định kỳ trong ứng dụng lịch của bạn
Sẵn sàng thiết lập chuyến đi thường xuyên của bạn? Tạo bữa tiệc định kỳ và để chúng tôi xử lý việc lên lịch!