Birçok bisiklet grubunun düzenli sürüşleri vardır - Pazartesi sabahı işe gidiş ekibi, ayın ilk Cumartesi macera sürüşü veya Çarşamba akşamı sosyal tur. Şimdiye kadar, organizatörler her etkinliği manuel olarak oluşturmak zorundaydı. Yeni Tekrarlayan Partiler özelliğimizle, bir kez zamanlama ayarlayabilir ve Party Onbici’nin otomatik olarak örnekler oluşturmasına izin verebilirsiniz.

Tek Seferlik Etkinliklerin Sorunu

Topluluk bisiklet gruplarının genellikle öngörülebilir zamanlamaları vardır:

  • “Her Salı ve Perşembe sabah 6:30’da”
  • “Her ayın ilk Pazar günü”
  • “Her iki haftada bir Cumartesi sabahı”

Bu etkinlikleri manuel olarak oluşturmak sıkıcı, hataya açık ve olaylar arasında tutarlı rotaları ve ayarları korumayı zorlaştırır.

iCalendar RRULE ile Tanışın

Tekrarlayan etkinlik sistemimizi iCalendar RRULE standardı (RFC 5545) üzerine kurduk. Bu, Google Calendar, Apple Calendar ve Outlook tarafından kullanılan aynı formattır - daha geniş takvim ekosistemiyle uyumluluk sağlar.

Örnek Tekrarlama Kalıpları

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR
  → Her Pazartesi, Çarşamba ve Cuma

RRULE:FREQ=MONTHLY;BYDAY=1SA
  → Her ayın ilk Cumartesisi

RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=SU
  → Her iki haftada bir Pazar

RRULE:FREQ=DAILY;COUNT=10
  → 10 kez için günlük

Veri Modeli

Bir RecurringParty, Party örnekleri oluşturan bir şablon görevi görür:

 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)

    # Zamanlama yapılandırması
    start_date = models.DateField()
    end_date = models.DateField(null=True, blank=True)
    max_occurrences = models.PositiveIntegerField(null=True, blank=True)
    recurrence = RecurrenceField()  # RRULE kalıbı

    # Şablon alanları (her örneğe kopyalanır)
    departure_time = models.TimeField()
    arrival_time = models.TimeField()
    origin = PointField()
    destination = PointField()
    route = models.JSONField()
    difficulty = models.CharField(choices=DIFFICULTY_CHOICES)
    # ... diğer parti özellikleri

    is_active = models.BooleanField(default=True)

Oluşturulan her Party örneği üst öğesine geri bağlanır:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Party(models.Model):
    # Mevcut alanlar...

    # Tekrarlayan parti desteği
    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)

Örnek Oluşturma

Tekrarlayan bir parti oluşturulduğunda veya zamanlama değiştiğinde, yaklaşan dönem için örnekler oluştururuz:

 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]:
    """Yaklaşan olaylar için parti örnekleri oluştur."""
    occurrences = self.recurrence.between(
        datetime.now().date(),
        datetime.now().date() + timedelta(days=lookahead_days),
    )

    created = []
    for date in occurrences:
        # Örnek zaten varsa atla
        if self.instances.filter(scheduled_date=date).exists():
            continue

        # Şablondan yeni parti örneği oluştur
        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,
            # ... diğer şablon alanlarını kopyala
        )
        created.append(party)

    return created

Celery Beat ile Otomatik Oluşturma

Günlük bir Celery görevi örnekleri güncel tutar:

1
2
3
4
5
6
7
8
9
@shared_task
def generate_recurring_party_instances():
    """Tüm aktif tekrarlayan partiler için parti örnekleri oluştur."""
    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} için {len(created)} örnek oluşturuldu"
            )

Celery Beat zamanlaması bunu gece yarısı çalıştırır:

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),  # Günlük gece yarısı
    },
}

Kullanıcı Arayüzü

Tekrarlayan Parti Oluşturma

Form, RRULE kalıpları oluşturmak için django-recurrence widget’ını kullanır:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class RecurringPartyForm(forms.ModelForm):
    recurrence = RecurrenceField(
        label="Tekrarlama kalıbı",
        help_text="Bu partinin ne zaman tekrarlanacağını ayarlayın.",
        required=True,
    )

    class Meta:
        model = RecurringParty
        fields = [
            "name", "start_date", "end_date", "recurrence",
            "departure_time", "arrival_time",
            "origin", "destination", "description",
            "difficulty", "gender", "privacy",
        ]

Widget, yaygın kalıplar için sezgisel bir arayüz sağlar:

  • Günlük / Haftalık / Aylık / Yıllık
  • Haftanın belirli günleri
  • Sıralı kalıplar (birinci, ikinci, son)
  • Özel aralıklar (her 2 haftada bir)
  • Bitiş koşulları (tarihe kadar, N olaydan sonra veya hiçbir zaman)

Örnekleri Yönetme

Organizatörler tüm oluşturulan örnekleri görüntüleyebilir ve:

  • Bireysel örnekleri düzenle - Bir olay için rota veya zamanı değiştir
  • Örnekleri iptal et - Belirli tarihleri iptal edildi olarak işaretle
  • Örnekleri yeniden oluştur - Sonraki 30 gün için manuel olarak oluşturmayı tetikle

Uç Durumları Ele Alma

Saat Dilimi Farkındalığı

Tüm tarihler organizatörün yapılandırılmış saat diliminde saklanır ve izleyicinin yerel saatinde görüntülenir:

1
2
3
# Saat dilimi farkındalıklı tarihler kullanarak örnekler oluştur
local_tz = get_current_timezone()
today = timezone.now().astimezone(local_tz).date()

Tatil ve İstisna İşleme

Kullanıcılar, tekrarlayan kalıbı etkilemeden bireysel örnekleri iptal edebilir:

1
2
3
party = recurring_party.instances.get(scheduled_date=holiday_date)
party.is_cancelled = True
party.save()

İptal edilen örnekler hala görsel bir göstergeyle listede görünür, karışıklığı önler.

Yetim Örnekler

Tekrarlayan bir parti silinirse, oluşturulan örnekler isteğe bağlı olarak korunabilir:

 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:
        # Gelecek örnekleri yumuşak silme
        recurring_party.instances.filter(
            scheduled_date__gte=date.today(),
            deleted_at__isnull=True,
        ).update(deleted_at=timezone.now())

    recurring_party.delete()

Kullanıcı Deneyimi

Haftalık sürüş oluşturmak sadece birkaç adım alır:

  1. Gösterge Paneli → Tekrarlayan Partiler → Oluştur‘a gidin
  2. Parti ayrıntılarınızı ayarlayın (rota, zamanlar, zorluk)
  3. Tekrarlama kalıbını yapılandırın (ör. “Her Cumartesi sabah 8’de”)
  4. Başlangıç tarihini ve isteğe bağlı bitiş tarihini ayarlayın
  5. Kaydet - örnekler otomatik olarak oluşturulur!

Sistem 30 gün öncesinden parti örnekleri oluşturur ve Celery zaman geçtikçe yenilerini oluşturmaya devam eder.

Bisiklet Toplulukları İçin Faydalar

  • Ayarla ve unut - Artık haftalık etkinlik oluşturma yok
  • Tutarlılık - Aynı rota, aynı zaman, aynı ayarlar
  • Esneklik - Bireysel olayları düzenle veya iptal et
  • Keşif - Bisikletçiler düzenli grup sürüşlerini kolayca bulabilir
  • Takvim entegrasyonu - Takvim uygulamanızda tekrarlayan etkinliklere abone olun

Düzenli sürüşünüzü ayarlamaya hazır mısınız? Tekrarlayan parti oluşturun ve zamanlamayı bize bırakın!