Files
scha/schasite/models.py
T
westfarn f37ff3e87b
Unit Tests / test (push) Successful in 3s
Add start/end time fields to CalendarEvent (#28) (#29)
## Summary
Closes #28.

- Add optional `start_time` / `end_time` (`TimeField`) on `CalendarEvent`
- Expose times in Django admin list + form
- Replace hardcoded calendar template times with `time_range_display`
- Add migration `0013` and unit coverage for time formatting

## Test plan
- [ ] `python manage.py migrate`
- [ ] `python manage.py test schasite.tests.CalendarEventTests`
- [ ] In admin, edit an event with start/end times and confirm calendar page shows them
- [ ] Confirm events without times hide the Time rowReviewed-on: #29
2026-08-04 11:00:24 -07:00

228 lines
9.0 KiB
Python

from django.db import models
from phonenumber_field.modelfields import PhoneNumberField
import datetime
from django.utils import timezone
from django.conf import settings
class TimeInfoBase(models.Model):
created = models.DateTimeField(default=timezone.now)
last_modified = models.DateTimeField(default=timezone.now)
class Meta:
abstract = True
def save(self, **kwargs):
if not kwargs.pop("skip_last_modified", False) and not hasattr(self, "skip_last_modified"):
self.last_modified = timezone.now()
if kwargs.get("update_fields") is not None:
kwargs["update_fields"] = list({*kwargs["update_fields"], "last_modified"})
super().save(**kwargs)
# Create your models here.
class UsefulLinks(TimeInfoBase):
name = models.CharField(max_length=256)
url = models.CharField(max_length=256)
class Membership(TimeInfoBase):
children = models.CharField(max_length=256, default="", blank=True, null=True)
def get_address_str(self):
if self.addressmodel1 is None:
return "No Address"
elif self.addressmodel1.address_1 is None:
return "No Address"
else:
return self.addressmodel1.address_1
def get_person_1(self):
emails = [
item.email
for item in MembershipPerson.objects.filter(membership_id=self.id)
]
# remove all the Nones
filtered_emails = [i for i in emails if i is not None]
if len(filtered_emails) == 0:
return "No Person"
else:
return " | ".join(filtered_emails)
def __str__(self):
return self.get_address_str() + " | " + self.get_person_1()
class AddressModel1(TimeInfoBase):
membership = models.OneToOneField(Membership, on_delete=models.CASCADE)
address_1 = models.CharField(max_length=128)
address_2 = models.CharField(max_length=128, blank=True)
city = models.CharField(max_length=128)
state = models.CharField(max_length=2)
zip_code = models.CharField(max_length=5)
class CalendarEvent(TimeInfoBase):
event_name = models.CharField(max_length=256)
start_date = models.DateField(blank=True, null=True)
end_date = models.DateField(blank=True, null=True)
start_time = models.TimeField(blank=True, null=True)
end_time = models.TimeField(blank=True, null=True)
location_name = models.CharField(max_length=256, blank=True, null=True)
coordinator_email = models.EmailField(max_length=256, blank=True, null=True)
event_link_name = models.CharField(max_length=64, blank=True, null=True)
event_url = models.URLField(max_length=256, blank=True, null=True)
#description= models.CharField(max_length=1024, default="")
def has_date(self):
return not self.start_date is None
def past_event(self):
if self.has_date():
return self.start_date <= datetime.datetime.now().date()
else:
return False
def future_event(self):
if self.has_date():
return self.start_date > datetime.datetime.now().date()
else:
return False
def no_date(self):
return not self.has_date()
def time_range_display(self):
"""Format start/end times for templates, e.g. '11:00 AM - 3:00 PM'."""
from django.utils.formats import time_format
if not self.start_time and not self.end_time:
return ""
start = time_format(self.start_time, "g:i A") if self.start_time else ""
end = time_format(self.end_time, "g:i A") if self.end_time else ""
if start and end:
return f"{start} - {end}"
return start or end
class CalendarEventAddressModel(TimeInfoBase):
calendar_event = models.OneToOneField(CalendarEvent, on_delete=models.CASCADE)
address_1 = models.CharField(max_length=128)
address_2 = models.CharField(max_length=128, blank=True)
city = models.CharField(max_length=128)
state = models.CharField(max_length=2)
zip_code = models.CharField(max_length=5)
def __str__(self):
return (
self.address_1 + ", " + self.city + ", " + self.state + " " + self.zip_code
)
class MembershipPerson(TimeInfoBase):
membership = models.ForeignKey(
Membership, on_delete=models.CASCADE, blank=True, null=True
)
first_name = models.CharField(max_length=256, blank=True, null=True)
last_name = models.CharField(max_length=256, blank=True, null=True)
phone_number = PhoneNumberField(blank=False, null=True)
email = models.EmailField(max_length=254, blank=True, null=True)
def __str__(self):
return self.email if self.email else "No email"
class MembershipCommittee(TimeInfoBase):
membership = models.OneToOneField(Membership, on_delete=models.CASCADE)
block_captain = models.BooleanField(default=False, blank=True, null=True)
coordinator = models.BooleanField(default=False, blank=True, null=True)
egg_hunt = models.BooleanField(default=False, blank=True, null=True)
spring_garage_sale = models.BooleanField(default=False, blank=True, null=True)
golf_outing = models.BooleanField(default=False, blank=True, null=True)
ice_cream_social = models.BooleanField(default=False, blank=True, null=True)
fall_garage_sale = models.BooleanField(default=False, blank=True, null=True)
halloween_party = models.BooleanField(default=False, blank=True, null=True)
santa_visit = models.BooleanField(default=False, blank=True, null=True)
website = models.BooleanField(default=False, blank=True, null=True)
civic_affair = models.BooleanField(default=False, blank=True, null=True)
phone_directory = models.BooleanField(default=False, blank=True, null=True)
no_preference = models.BooleanField(default=False, blank=True, null=True)
class MembershipServices(TimeInfoBase):
membership = models.OneToOneField(Membership, on_delete=models.CASCADE)
babysitting = models.BooleanField(default=False, blank=True, null=True)
lawn_mowing = models.BooleanField(default=False, blank=True, null=True)
snow_shoveling = models.BooleanField(default=False, blank=True, null=True)
leaf_raking = models.BooleanField(default=False, blank=True, null=True)
petsitting = models.BooleanField(default=False, blank=True, null=True)
house_sitting = models.BooleanField(default=False, blank=True, null=True)
other = models.BooleanField(default=False, blank=True, null=True)
other_desc = models.CharField(default="", blank=True, null=True, max_length=256)
class Payments(TimeInfoBase):
date = models.DateField(default=timezone.now)
status = models.CharField(default="Completed", max_length=256)
email = models.EmailField(blank=True, null=True)
person = models.ForeignKey(
MembershipPerson, on_delete=models.CASCADE, blank=True, null=True
)
class SCHAOfficer(TimeInfoBase):
name = models.CharField(max_length=255)
position = models.CharField(max_length=255)
email = models.EmailField(max_length=255)
### NOT USED YET ###
class CommunitySchools(TimeInfoBase):
name = models.CharField(max_length=255)
name_school_url = models.URLField(max_length=256)
rating_url = models.URLField(max_length=256)
rating = models.DecimalField(max_digits=5, decimal_places=2)
distance = models.DecimalField(max_digits=3, decimal_places=1)
principal = models.CharField(max_length=255)
grades = models.CharField(max_length=255)
class CommunityShoppingAndDining(TimeInfoBase):
name = models.CharField(max_length=255)
distance = models.DecimalField(max_digits=3, decimal_places=1)
description = models.CharField(max_length=1024)
class CommunityParks(TimeInfoBase):
name = models.CharField(max_length=255)
distance = models.DecimalField(max_digits=3, decimal_places=1)
description = models.CharField(max_length=1024)
### Member only models
class CommunityMember(TimeInfoBase):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
membership_person = models.OneToOneField(MembershipPerson, on_delete=models.CASCADE)
class CommunityPost(TimeInfoBase):
title = models.CharField(max_length=255)
category = models.CharField(max_length=255)
content = models.CharField(max_length=1024*8)
likes = models.IntegerField(default=0)
author = models.OneToOneField(CommunityMember, on_delete=models.CASCADE, blank=True, null=True)
class CommunityPostReports(TimeInfoBase):
# for anyone who reports a post
post = models.ForeignKey(CommunityPost, on_delete=models.CASCADE)
reporter = models.OneToOneField(CommunityMember, on_delete=models.CASCADE, blank=True, null=True)
class CommunityPostLikes(TimeInfoBase):
# for anyone who likes a post
post = models.ForeignKey(CommunityPost, on_delete=models.CASCADE)
reporter = models.OneToOneField(CommunityMember, on_delete=models.CASCADE)
class CommunityComment(TimeInfoBase):
post = models.ForeignKey(CommunityPost, on_delete=models.CASCADE)
content = models.CharField(max_length=1024*8)
likes = models.IntegerField(default=0)
author = models.OneToOneField(CommunityMember, on_delete=models.CASCADE, blank=True, null=True)