|
4 | 4 | from django.dispatch import receiver
|
5 | 5 | from email_tools.emails import send_email
|
6 | 6 | from phonenumber_field.modelfields import PhoneNumberField
|
7 |
| -from schedule.models import Event, Occurrence |
| 7 | +from schedule.models import Event |
| 8 | +from django.urls import reverse |
| 9 | +from datetime import timedelta |
8 | 10 |
|
9 | 11 | User = settings.AUTH_USER_MODEL
|
10 | 12 |
|
@@ -447,13 +449,168 @@ class Meta:
|
447 | 449 |
|
448 | 450 | def __str__(self):
|
449 | 451 | return f"{self.user}: {self.metric}"
|
| 452 | + |
| 453 | +class Occurrence(models.Model): |
| 454 | + event = models.ForeignKey(Event, on_delete=models.CASCADE, verbose_name=("event"), related_name="occurrences") |
| 455 | + title = models.CharField(("title"), max_length=255, blank=True) |
| 456 | + location = models.CharField(("location"), max_length=255, blank=True) |
| 457 | + description = models.TextField(("description"), blank=True) |
| 458 | + start = models.DateTimeField(("start"), db_index=True) |
| 459 | + end = models.DateTimeField(("end"), db_index=True) |
| 460 | + cancelled = models.BooleanField(("cancelled"), default=False) |
| 461 | + original_start = models.DateTimeField(("original start")) |
| 462 | + original_end = models.DateTimeField(("original end")) |
| 463 | + created_on = models.DateTimeField(("created on"), auto_now_add=True) |
| 464 | + updated_on = models.DateTimeField(("updated on"), auto_now=True) |
| 465 | + interval = models.IntegerField(("interval"), blank=True, validators=[MinValueValidator(5), MaxValueValidator(60)]) |
| 466 | + |
| 467 | + class Meta: |
| 468 | + verbose_name = ("occurrence") |
| 469 | + verbose_name_plural = ("occurrences") |
| 470 | + index_together = (("start", "end"),) |
| 471 | + |
| 472 | + def __init__(self, *args, **kwargs): |
| 473 | + super().__init__(*args, **kwargs) |
| 474 | + event = kwargs.get("event", None) |
| 475 | + if not self.title and event: |
| 476 | + self.title = event.title |
| 477 | + if not self.description and event: |
| 478 | + self.description = event.description |
| 479 | + if not self.location and event: |
| 480 | + self.location = event.location |
| 481 | + |
| 482 | + def save(self, *args, **kwargs): |
| 483 | + super().save(*args, **kwargs) |
| 484 | + |
| 485 | + if self.pk: # If save is called on object update, not creation |
| 486 | + self.bookings.all().delete() |
| 487 | + |
| 488 | + delta = self.end - self.start |
| 489 | + delta_minutes = delta.total_seconds() / 60 |
| 490 | + booking_count = int(delta_minutes // self.interval) |
| 491 | + for i in range(booking_count): |
| 492 | + booking_start = self.start + timedelta(minutes=i * self.interval) |
| 493 | + booking_end = booking_start + timedelta(minutes=self.interval) |
| 494 | + Booking.objects.create( |
| 495 | + occurrence=self, |
| 496 | + user=None, |
| 497 | + start=booking_start, |
| 498 | + end = booking_end, |
| 499 | + ) |
| 500 | + |
| 501 | + def moved(self): |
| 502 | + return self.original_start != self.start or self.original_end != self.end |
| 503 | + |
| 504 | + moved = property(moved) |
| 505 | + |
| 506 | + def move(self, new_start, new_end): |
| 507 | + self.start = new_start |
| 508 | + self.end = new_end |
| 509 | + self.save() |
| 510 | + |
| 511 | + def cancel(self): |
| 512 | + self.cancelled = True |
| 513 | + self.save() |
| 514 | + |
| 515 | + def uncancel(self): |
| 516 | + self.cancelled = False |
| 517 | + self.save() |
| 518 | + |
| 519 | + @property |
| 520 | + def seconds(self): |
| 521 | + return (self.end - self.start).total_seconds() |
| 522 | + |
| 523 | + @property |
| 524 | + def minutes(self): |
| 525 | + return float(self.seconds) / 60 |
| 526 | + |
| 527 | + @property |
| 528 | + def hours(self): |
| 529 | + return float(self.seconds) / 3600 |
| 530 | + |
| 531 | + def get_absolute_url(self): |
| 532 | + if self.pk is not None: |
| 533 | + return reverse( |
| 534 | + "occurrence", |
| 535 | + kwargs={"occurrence_id": self.pk, "event_id": self.event_id}, |
| 536 | + ) |
| 537 | + return reverse( |
| 538 | + "occurrence_by_date", |
| 539 | + kwargs={ |
| 540 | + "event_id": self.event_id, |
| 541 | + "year": self.start.year, |
| 542 | + "month": self.start.month, |
| 543 | + "day": self.start.day, |
| 544 | + "hour": self.start.hour, |
| 545 | + "minute": self.start.minute, |
| 546 | + "second": self.start.second, |
| 547 | + }, |
| 548 | + ) |
| 549 | + |
| 550 | + def get_cancel_url(self): |
| 551 | + if self.pk is not None: |
| 552 | + return reverse( |
| 553 | + "cancel_occurrence", |
| 554 | + kwargs={"occurrence_id": self.pk, "event_id": self.event_id}, |
| 555 | + ) |
| 556 | + return reverse( |
| 557 | + "cancel_occurrence_by_date", |
| 558 | + kwargs={ |
| 559 | + "event_id": self.event_id, |
| 560 | + "year": self.start.year, |
| 561 | + "month": self.start.month, |
| 562 | + "day": self.start.day, |
| 563 | + "hour": self.start.hour, |
| 564 | + "minute": self.start.minute, |
| 565 | + "second": self.start.second, |
| 566 | + }, |
| 567 | + ) |
| 568 | + |
| 569 | + def get_edit_url(self): |
| 570 | + if self.pk is not None: |
| 571 | + return reverse( |
| 572 | + "edit_occurrence", |
| 573 | + kwargs={"occurrence_id": self.pk, "event_id": self.event_id}, |
| 574 | + ) |
| 575 | + return reverse( |
| 576 | + "edit_occurrence_by_date", |
| 577 | + kwargs={ |
| 578 | + "event_id": self.event_id, |
| 579 | + "year": self.start.year, |
| 580 | + "month": self.start.month, |
| 581 | + "day": self.start.day, |
| 582 | + "hour": self.start.hour, |
| 583 | + "minute": self.start.minute, |
| 584 | + "second": self.start.second, |
| 585 | + }, |
| 586 | + ) |
| 587 | + |
| 588 | + def __str__(self): |
| 589 | + start_str = self.start.strftime("%Y-%m-%d %H:%M:%S") |
| 590 | + end_str = self.end.strftime("%Y-%m-%d %H:%M:%S") |
| 591 | + return f"{start_str} to {end_str}" |
| 592 | + |
| 593 | + def __lt__(self, other): |
| 594 | + return self.end < other.end |
| 595 | + |
| 596 | + def __hash__(self): |
| 597 | + if not self.pk: |
| 598 | + raise TypeError("Model instances without primary key value are unhashable") |
| 599 | + return hash(self.pk) |
| 600 | + |
| 601 | + def __eq__(self, other): |
| 602 | + return ( |
| 603 | + isinstance(other, Occurrence) |
| 604 | + and self.original_start == other.original_start |
| 605 | + and self.original_end == other.original_end |
| 606 | + ) |
450 | 607 |
|
451 | 608 | class Booking(models.Model):
|
452 | 609 | """
|
453 | 610 | Booking within an occurrence
|
454 | 611 | """
|
455 | 612 |
|
456 |
| - occurrence = models.ForeignKey(Occurrence, on_delete=models.CASCADE) |
| 613 | + occurrence = models.ForeignKey(Occurrence, on_delete=models.CASCADE, related_name="bookings") |
457 | 614 | user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
|
458 | 615 | start = models.DateTimeField("start", db_index=True)
|
459 | 616 | end = models.DateTimeField("end", db_index=True)
|
|
0 commit comments