redesign proto

This commit is contained in:
2025-04-15 12:18:03 -05:00
parent cdbbea7711
commit b9aee578c3
30 changed files with 2899 additions and 397 deletions

View File

@@ -1,4 +1,13 @@
stripe
django
django-adminplus
django-recaptcha
asgiref==3.8.1
certifi==2025.1.31
charset-normalizer==3.4.1
Django==5.2
django-phonenumber-field==8.1.0
django-recaptcha==4.1.0
idna==3.10
phonenumbers==9.0.3
requests==2.32.3
sqlparse==0.5.3
stripe==12.0.0
typing_extensions==4.13.2
urllib3==2.4.0

View File

@@ -11,6 +11,6 @@ import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'scha.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "scha.settings")
application = get_asgi_application()

View File

@@ -20,7 +20,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-0&6)5#t(wghsn$q7pg#s!x+_!s_y#labf6dh*8q%34q25iuag*'
SECRET_KEY = "django-insecure-0&6)5#t(wghsn$q7pg#s!x+_!s_y#labf6dh*8q%34q25iuag*"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
@@ -31,55 +31,55 @@ ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'schasite.apps.SchasiteConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'phonenumber_field',
'django_recaptcha',
"schasite.apps.SchasiteConfig",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"phonenumber_field",
"django_recaptcha",
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = 'scha.urls'
ROOT_URLCONF = "scha.urls"
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = 'scha.wsgi.application'
WSGI_APPLICATION = "scha.wsgi.application"
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
@@ -89,16 +89,16 @@ DATABASES = {
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
@@ -106,9 +106,9 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = "en-us"
TIME_ZONE = 'UTC'
TIME_ZONE = "UTC"
USE_I18N = True
@@ -118,16 +118,18 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = 'static/'
STATIC_URL = "static/"
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
STRIPE_PUBLISHABLE_KEY = 'pk_test_51OxENZDV0RPXOyxGkZnncLFbpVoQq6qxUSN08BPDTpHjrm2UTtvOrBG2A2IQhX3oxdYd6amGaci00SKchYZ5DUUN00jTBzhh62'
STRIPE_SECRET_KEY = 'sk_test_51OxENZDV0RPXOyxGkULVr0MvVvXgNWsKjWiP54a9ls02LgsgXnzEwP0IQ0VjvP4Wt4RrPx1FvzHSbLvChQGOKvv800Ui0NBvIA'
STRIPE_ENDPOINT_SECRET = 'whsec_a38b2d115b64d86713397e2718e2d42e5d263139dfbac6c0badc6f9ca8f72557'
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
STRIPE_PUBLISHABLE_KEY = "pk_test_51OxENZDV0RPXOyxGkZnncLFbpVoQq6qxUSN08BPDTpHjrm2UTtvOrBG2A2IQhX3oxdYd6amGaci00SKchYZ5DUUN00jTBzhh62"
STRIPE_SECRET_KEY = "sk_test_51OxENZDV0RPXOyxGkULVr0MvVvXgNWsKjWiP54a9ls02LgsgXnzEwP0IQ0VjvP4Wt4RrPx1FvzHSbLvChQGOKvv800Ui0NBvIA"
STRIPE_ENDPOINT_SECRET = (
"whsec_a38b2d115b64d86713397e2718e2d42e5d263139dfbac6c0badc6f9ca8f72557"
)
# Recaptcha Stuff
RECAPTCHA_PUBLIC_KEY = '6LesfrkpAAAAAGJ5vYp4KuuGcyfk70HkihCwp3d3'
RECAPTCHA_PRIVATE_KEY = '6LesfrkpAAAAABCcDGli5cZiq1hfS0lfRiXg0mlI'
RECAPTCHA_PUBLIC_KEY = "6LesfrkpAAAAAGJ5vYp4KuuGcyfk70HkihCwp3d3"
RECAPTCHA_PRIVATE_KEY = "6LesfrkpAAAAABCcDGli5cZiq1hfS0lfRiXg0mlI"

View File

@@ -14,6 +14,7 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
from django.conf.urls.static import static
@@ -23,8 +24,8 @@ from schasite.views import stripe_cancelled, stripe_success, stripe_webhook
urlpatterns = [
path("schasite/", include("schasite.urls")),
path('success/', stripe_success),
path('cancelled/', stripe_cancelled),
path('webhook/', stripe_webhook),
path('admin/', admin.site.urls),
path("success/", stripe_success),
path("cancelled/", stripe_cancelled),
path("webhook/", stripe_webhook),
path("admin/", admin.site.urls),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View File

@@ -11,6 +11,6 @@ import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'scha.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "scha.settings")
application = get_wsgi_application()

View File

@@ -1,192 +1,250 @@
from django.contrib import admin
from .models import UsefulLinks, Membership,CalendarEvent, MembershipServices, AddressModel1, MembershipPerson, MembershipCommittee, CalendarEventAddressModel, Payments
from .models import (
UsefulLinks,
Membership,
CalendarEvent,
MembershipServices,
AddressModel1,
MembershipPerson,
MembershipCommittee,
CalendarEventAddressModel,
Payments,
SCHAOfficer,
)
from django.http import HttpResponse
from datetime import datetime
from .forms import PaymentImport
# Register your models here.
class UsefulLinksAdmin(admin.ModelAdmin):
list_display = ["name", "url"]
class MembershipAddressInline(admin.TabularInline):
model = AddressModel1
extra = 1
readonly_fields = ('id',)
readonly_fields = ("id",)
class MembershipPersonInline(admin.TabularInline):
model = MembershipPerson
extra = 1
readonly_fields = ('id',)
readonly_fields = ("id",)
class MembershipCommiteeInline(admin.TabularInline):
model = MembershipCommittee
extra = 1
readonly_fields = ('id',)
readonly_fields = ("id",)
class MembershipServicesInline(admin.TabularInline):
model = MembershipServices
extra = 1
readonly_fields = ('id',)
readonly_fields = ("id",)
def download_csv_by_members(modelAdmin, request, queryset):
import csv
import csv
import io as StringIO
def stream_csv(queryset):
csvfile = StringIO.StringIO()
writer = csv.writer(csvfile)
writer.writerow([
'address_1',
'city',
'state',
'zip_code',
'first_name',
'last_name',
'email',
'phone_number'
])
writer.writerow(
[
"address_1",
"city",
"state",
"zip_code",
"first_name",
"last_name",
"email",
"phone_number",
]
)
for q in queryset:
people = [item for item in MembershipPerson.objects.filter(membership_id=q.id)]
people = [
item for item in MembershipPerson.objects.filter(membership_id=q.id)
]
for person in people:
writer.writerow([
q.addressmodel1.address_1,
q.addressmodel1.city,
q.addressmodel1.state,
q.addressmodel1.zip_code,
person.first_name,
person.last_name,
person.email,
person.phone_number
])
writer.writerow(
[
q.addressmodel1.address_1,
q.addressmodel1.city,
q.addressmodel1.state,
q.addressmodel1.zip_code,
person.first_name,
person.last_name,
person.email,
person.phone_number,
]
)
yield csvfile.getvalue()
now = datetime.now()
filename = now.strftime("%Y_%m_%d_%H_%M_%S") + "_scha_member_by_member.csv"
response = HttpResponse(stream_csv(queryset), content_type="text/csv")
response['Content-Disposition'] = "attachment; filename={}".format(filename)
response["Content-Disposition"] = "attachment; filename={}".format(filename)
return response
def download_csv_by_address(modeladmin, request, queryset):
import csv
import io as StringIO
def stream_csv(queryset):
csvfile = StringIO.StringIO()
writer = csv.writer(csvfile)
writer.writerow(['address_1',
"city",
"state",
"zip_code",
"person_1_email",
"person_1_phone",
"person_1_first_name",
"person_1_last_name",
"person_2_email",
"person_2_phone",
"person_2_first_name",
"person_2_last_name"])
writer.writerow(
[
"address_1",
"city",
"state",
"zip_code",
"person_1_email",
"person_1_phone",
"person_1_first_name",
"person_1_last_name",
"person_2_email",
"person_2_phone",
"person_2_first_name",
"person_2_last_name",
]
)
for q in queryset:
people = [item for item in MembershipPerson.objects.filter(membership_id=q.id)]
writer.writerow([
q.addressmodel1.address_1,
q.addressmodel1.city,
q.addressmodel1.state,
q.addressmodel1.zip_code,
people[0].email if len(people) > 0 else "",
people[0].phone_number if len(people) > 0 else "",
people[0].first_name if len(people) > 0 else "",
people[0].last_name if len(people) > 0 else "",
people[1].email if len(people) > 1 else "",
people[1].phone_number if len(people) > 1 else "",
people[1].first_name if len(people) > 1 else "",
people[1].last_name if len(people) > 1 else "",
])
people = [
item for item in MembershipPerson.objects.filter(membership_id=q.id)
]
writer.writerow(
[
q.addressmodel1.address_1,
q.addressmodel1.city,
q.addressmodel1.state,
q.addressmodel1.zip_code,
people[0].email if len(people) > 0 else "",
people[0].phone_number if len(people) > 0 else "",
people[0].first_name if len(people) > 0 else "",
people[0].last_name if len(people) > 0 else "",
people[1].email if len(people) > 1 else "",
people[1].phone_number if len(people) > 1 else "",
people[1].first_name if len(people) > 1 else "",
people[1].last_name if len(people) > 1 else "",
]
)
yield csvfile.getvalue()
now = datetime.now()
filename = now.strftime("%Y_%m_%d_%H_%M_%S") + "_scha_member_by_address.csv"
response = HttpResponse(stream_csv(queryset), content_type="text/csv")
response['Content-Disposition'] = "attachment; filename={}".format(filename)
response["Content-Disposition"] = "attachment; filename={}".format(filename)
return response
class MembershipAdmin(admin.ModelAdmin):
inlines = [MembershipAddressInline, MembershipPersonInline, MembershipCommiteeInline, MembershipServicesInline]
actions=[download_csv_by_address, download_csv_by_members]
inlines = [
MembershipAddressInline,
MembershipPersonInline,
MembershipCommiteeInline,
MembershipServicesInline,
]
actions = [download_csv_by_address, download_csv_by_members]
class CalendarEventAddressInline(admin.TabularInline):
model = CalendarEventAddressModel
extra = 1
readonly_fields = ('id',)
readonly_fields = ("id",)
class CalendarEventAdmin(admin.ModelAdmin):
inlines = [CalendarEventAddressInline]
list_display = ["event_name", "start_date", "end_date", "coordinator_email", "event_link_name"]
list_display = [
"event_name",
"start_date",
"end_date",
"coordinator_email",
"event_link_name",
]
class AddressModelAdmin(admin.ModelAdmin):
pass
class MembershipPersonAdmin(admin.ModelAdmin):
pass
class MembershipCommitteeAdmin(admin.ModelAdmin):
pass
class MembershipServicesAdmin(admin.ModelAdmin):
pass
class CalendarEventAddressModelAdmin(admin.ModelAdmin):
pass
def download_payments(modelAdmin, request, queryset):
import csv
import csv
import io as StringIO
def stream_payment_csv(queryset):
csvfile = StringIO.StringIO()
writer = csv.writer(csvfile)
writer.writerow([
'email',
'date',
'status',
'first_name',
'last_name',
'phone_number'
])
writer.writerow(
["email", "date", "status", "first_name", "last_name", "phone_number"]
)
for q in queryset:
first_name=""
first_name = ""
last_name = ""
phone_number = ""
if q.person:
first_name = q.person.first_name if q.person.first_name else ""
last_name = q.person.last_name if q.person.last_name else ""
phone_number = q.person.phone_number if q.person.phone_number else ""
writer.writerow([
q.email,
q.date,
q.status,
first_name,
last_name,
phone_number,
])
writer.writerow(
[
q.email,
q.date,
q.status,
first_name,
last_name,
phone_number,
]
)
yield csvfile.getvalue()
now = datetime.now()
filename = now.strftime("%Y_%m_%d_%H_%M_%S") + "_scha_payments_by_member.csv"
response = HttpResponse(stream_payment_csv(queryset), content_type="text/csv")
response['Content-Disposition'] = "attachment; filename={}".format(filename)
response["Content-Disposition"] = "attachment; filename={}".format(filename)
return response
class PaymentsAdmin(admin.ModelAdmin):
list_display = ["date", "status", "email"]
search_fields = ['email']
actions=[download_payments]
search_fields = ["email"]
actions = [download_payments]
form = PaymentImport
class SCHAOfficerAdmin(admin.ModelAdmin):
list_display = ["position", "name", "email"]
admin.site.register(UsefulLinks, UsefulLinksAdmin)
admin.site.register(Membership, MembershipAdmin)
admin.site.register(CalendarEvent, CalendarEventAdmin)
admin.site.register(AddressModel1, AddressModelAdmin)
admin.site.register(AddressModel1, AddressModelAdmin)
admin.site.register(MembershipPerson, MembershipPersonAdmin)
admin.site.register(MembershipCommittee, MembershipCommitteeAdmin)
admin.site.register(MembershipServices, MembershipServicesAdmin)
admin.site.register(CalendarEventAddressModel, CalendarEventAddressModelAdmin)
admin.site.register(Payments, PaymentsAdmin)
admin.site.register(MembershipCommittee, MembershipCommitteeAdmin)
admin.site.register(MembershipServices, MembershipServicesAdmin)
admin.site.register(CalendarEventAddressModel, CalendarEventAddressModelAdmin)
admin.site.register(Payments, PaymentsAdmin)
admin.site.register(SCHAOfficer, SCHAOfficerAdmin)

View File

@@ -2,5 +2,5 @@ from django.apps import AppConfig
class SchasiteConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'schasite'
default_auto_field = "django.db.models.BigAutoField"
name = "schasite"

View File

@@ -1,8 +1,15 @@
from django import forms
from django.forms import ModelForm
from .models import Membership, AddressModel1, MembershipPerson, MembershipCommittee, MembershipServices
from .models import (
Membership,
AddressModel1,
MembershipPerson,
MembershipCommittee,
MembershipServices,
)
from phonenumber_field.formfields import PhoneNumberField
from django.core.exceptions import ValidationError
# from django_recaptcha.fields import ReCaptchaField
# from django.conf import settings
# from django_recaptcha.widgets import ReCaptchaV3
@@ -19,45 +26,81 @@ from django.core.exceptions import ValidationError
# )
class ChildrenForm(ModelForm):
class Meta:
model = Membership
fields = ["children"]
class AddressForm(ModelForm):
class Meta:
model = AddressModel1
fields = ["address_1","address_2","city","state","zip_code"]
fields = ["address_1", "address_2", "city", "state", "zip_code"]
class PeopleForm(ModelForm):
phone_number = PhoneNumberField(required=False)
def clean(self):
cleaned_data = super().clean()
if cleaned_data.get('phone_number'):
if len(cleaned_data.get('first_name')) == 0 or len(cleaned_data.get('last_name')) == 0 or len(cleaned_data.get('email')) == 0 or len(cleaned_data.get('phone_number').raw_input) == 0:
raise ValidationError ('A Field is missing')
if cleaned_data.get("phone_number"):
if (
len(cleaned_data.get("first_name")) == 0
or len(cleaned_data.get("last_name")) == 0
or len(cleaned_data.get("email")) == 0
or len(cleaned_data.get("phone_number").raw_input) == 0
):
raise ValidationError("A Field is missing")
else:
raise ValidationError('No phone number provided')
raise ValidationError("No phone number provided")
class Meta:
model = MembershipPerson
fields = ["first_name","last_name","phone_number","email"]
fields = ["first_name", "last_name", "phone_number", "email"]
class CommitteeForm(ModelForm):
block_captain = forms.BooleanField(label="I can serve as a Block Captain (distribute newsletters & directories)", required=False)
coordinator = forms.BooleanField(label="I can serve as a Coordinator, SCHA Board Member Position (oversee & organize block captains)", required=False)
block_captain = forms.BooleanField(
label="I can serve as a Block Captain (distribute newsletters & directories)",
required=False,
)
coordinator = forms.BooleanField(
label="I can serve as a Coordinator, SCHA Board Member Position (oversee & organize block captains)",
required=False,
)
egg_hunt = forms.BooleanField(label="Easter Egg Hunt (late March)", required=False)
spring_garage_sale = forms.BooleanField(label="Spring Garage Sale (May/June)", required=False)
spring_garage_sale = forms.BooleanField(
label="Spring Garage Sale (May/June)", required=False
)
golf_outing = forms.BooleanField(label="Golf Outing (Summer)", required=False)
ice_cream_social = forms.BooleanField(label="Ice Creame Social (August)", required=False)
fall_garage_sale = forms.BooleanField(label="Fall Garage Sale (September/October)", required=False)
halloween_party = forms.BooleanField(label="Halloween Party (October)", required=False)
ice_cream_social = forms.BooleanField(
label="Ice Creame Social (August)", required=False
)
fall_garage_sale = forms.BooleanField(
label="Fall Garage Sale (September/October)", required=False
)
halloween_party = forms.BooleanField(
label="Halloween Party (October)", required=False
)
santa_visit = forms.BooleanField(label="Santa Visits (December)", required=False)
website = forms.BooleanField(label="SCHA Newsletter/Website (wite articles, edit, report, etc)", required=False)
civic_affair = forms.BooleanField(label="Civic Affairs Journalist (report on school board/city council mtgs)", required=False)
phone_directory = forms.BooleanField(label="Annual Phone Directory (Help compile, edit, produce our Annual Neighborhood Directory)", required=False)
no_preference = forms.BooleanField(label="No Preference. I want to help in some way . Please email me.", required=False)
website = forms.BooleanField(
label="SCHA Newsletter/Website (wite articles, edit, report, etc)",
required=False,
)
civic_affair = forms.BooleanField(
label="Civic Affairs Journalist (report on school board/city council mtgs)",
required=False,
)
phone_directory = forms.BooleanField(
label="Annual Phone Directory (Help compile, edit, produce our Annual Neighborhood Directory)",
required=False,
)
no_preference = forms.BooleanField(
label="No Preference. I want to help in some way . Please email me.",
required=False,
)
class Meta:
model = MembershipCommittee
fields = [
@@ -76,6 +119,7 @@ class CommitteeForm(ModelForm):
"no_preference",
]
class ServicesForm(ModelForm):
babysitting = forms.BooleanField(label="Babysitting", required=False)
lawn_mowing = forms.BooleanField(label="Lawn Mowing", required=False)
@@ -84,6 +128,7 @@ class ServicesForm(ModelForm):
petsitting = forms.BooleanField(label="Petsitting", required=False)
house_sitting = forms.BooleanField(label="Housesitting", required=False)
other = forms.BooleanField(label="Other", required=False)
class Meta:
model = MembershipServices
fields = [
@@ -95,7 +140,8 @@ class ServicesForm(ModelForm):
"house_sitting",
"other",
"other_desc",
]
]
class PaymentImport(ModelForm):
pass
pass

View File

@@ -9,91 +9,186 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='AddressModel',
name="AddressModel",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('address_1', models.CharField(max_length=128)),
('address_2', models.CharField(blank=True, max_length=128)),
('city', models.CharField(max_length=128)),
('state', models.CharField(max_length=2)),
('zip_code', models.CharField(max_length=5)),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("address_1", models.CharField(max_length=128)),
("address_2", models.CharField(blank=True, max_length=128)),
("city", models.CharField(max_length=128)),
("state", models.CharField(max_length=2)),
("zip_code", models.CharField(max_length=5)),
],
),
migrations.CreateModel(
name='Membership',
name="Membership",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('children', models.CharField(default='', max_length=256)),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("children", models.CharField(default="", max_length=256)),
],
),
migrations.CreateModel(
name='UsefulLinks',
name="UsefulLinks",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=256)),
('url', models.CharField(max_length=256)),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=256)),
("url", models.CharField(max_length=256)),
],
),
migrations.CreateModel(
name='MembershipPerson',
name="MembershipPerson",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(blank=True, max_length=256, null=True)),
('last_name', models.CharField(blank=True, max_length=256, null=True)),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, region=None, unique=True)),
('email', models.EmailField(blank=True, max_length=254, null=True)),
('membership', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='schasite.membership')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("first_name", models.CharField(blank=True, max_length=256, null=True)),
("last_name", models.CharField(blank=True, max_length=256, null=True)),
(
"phone_number",
phonenumber_field.modelfields.PhoneNumberField(
max_length=128, null=True, region=None, unique=True
),
),
("email", models.EmailField(blank=True, max_length=254, null=True)),
(
"membership",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="schasite.membership",
),
),
],
),
migrations.CreateModel(
name='MembershipCommittee',
name="MembershipCommittee",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('block_captain', models.BooleanField(default=False)),
('coordinator', models.BooleanField(default=False)),
('egg_hunt', models.BooleanField(default=False)),
('spring_garage_sale', models.BooleanField(default=False)),
('golf_outing', models.BooleanField(default=False)),
('ice_cream_social', models.BooleanField(default=False)),
('fall_garage_sale', models.BooleanField(default=False)),
('halloween_party', models.BooleanField(default=False)),
('santa_visit', models.BooleanField(default=False)),
('website', models.BooleanField(default=False)),
('civic_affair', models.BooleanField(default=False)),
('phone_directory', models.BooleanField(default=False)),
('no_preference', models.BooleanField(default=False)),
('membership', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='schasite.membership')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("block_captain", models.BooleanField(default=False)),
("coordinator", models.BooleanField(default=False)),
("egg_hunt", models.BooleanField(default=False)),
("spring_garage_sale", models.BooleanField(default=False)),
("golf_outing", models.BooleanField(default=False)),
("ice_cream_social", models.BooleanField(default=False)),
("fall_garage_sale", models.BooleanField(default=False)),
("halloween_party", models.BooleanField(default=False)),
("santa_visit", models.BooleanField(default=False)),
("website", models.BooleanField(default=False)),
("civic_affair", models.BooleanField(default=False)),
("phone_directory", models.BooleanField(default=False)),
("no_preference", models.BooleanField(default=False)),
(
"membership",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
to="schasite.membership",
),
),
],
),
migrations.CreateModel(
name='CalendarEvent',
name="CalendarEvent",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('event_name', models.CharField(max_length=256)),
('start_date', models.DateField(blank=True, null=True)),
('end_date', models.DateField(blank=True, null=True)),
('location_name', models.CharField(blank=True, max_length=256, null=True)),
('coordinator_email', models.EmailField(blank=True, max_length=256, null=True)),
('event_link_name', models.CharField(blank=True, max_length=64, null=True)),
('event_url', models.URLField(blank=True, max_length=256, null=True)),
('location_address', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='schasite.addressmodel')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("event_name", models.CharField(max_length=256)),
("start_date", models.DateField(blank=True, null=True)),
("end_date", models.DateField(blank=True, null=True)),
(
"location_name",
models.CharField(blank=True, max_length=256, null=True),
),
(
"coordinator_email",
models.EmailField(blank=True, max_length=256, null=True),
),
(
"event_link_name",
models.CharField(blank=True, max_length=64, null=True),
),
("event_url", models.URLField(blank=True, max_length=256, null=True)),
(
"location_address",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="schasite.addressmodel",
),
),
],
),
migrations.CreateModel(
name='AddressModel1',
name="AddressModel1",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('address_1', models.CharField(max_length=128)),
('address_2', models.CharField(blank=True, max_length=128)),
('city', models.CharField(max_length=128)),
('state', models.CharField(max_length=2)),
('zip_code', models.CharField(max_length=5)),
('membership', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='schasite.membership')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("address_1", models.CharField(max_length=128)),
("address_2", models.CharField(blank=True, max_length=128)),
("city", models.CharField(max_length=128)),
("state", models.CharField(max_length=2)),
("zip_code", models.CharField(max_length=5)),
(
"membership",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
to="schasite.membership",
),
),
],
),
]

View File

@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('schasite', '0001_initial'),
("schasite", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name='membership',
name='children',
field=models.CharField(blank=True, default='', max_length=256, null=True),
model_name="membership",
name="children",
field=models.CharField(blank=True, default="", max_length=256, null=True),
),
]

View File

@@ -6,73 +6,73 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('schasite', '0002_alter_membership_children'),
("schasite", "0002_alter_membership_children"),
]
operations = [
migrations.AlterField(
model_name='membershipcommittee',
name='block_captain',
model_name="membershipcommittee",
name="block_captain",
field=models.BooleanField(blank=True, default=False, null=True),
),
migrations.AlterField(
model_name='membershipcommittee',
name='civic_affair',
model_name="membershipcommittee",
name="civic_affair",
field=models.BooleanField(blank=True, default=False, null=True),
),
migrations.AlterField(
model_name='membershipcommittee',
name='coordinator',
model_name="membershipcommittee",
name="coordinator",
field=models.BooleanField(blank=True, default=False, null=True),
),
migrations.AlterField(
model_name='membershipcommittee',
name='egg_hunt',
model_name="membershipcommittee",
name="egg_hunt",
field=models.BooleanField(blank=True, default=False, null=True),
),
migrations.AlterField(
model_name='membershipcommittee',
name='fall_garage_sale',
model_name="membershipcommittee",
name="fall_garage_sale",
field=models.BooleanField(blank=True, default=False, null=True),
),
migrations.AlterField(
model_name='membershipcommittee',
name='golf_outing',
model_name="membershipcommittee",
name="golf_outing",
field=models.BooleanField(blank=True, default=False, null=True),
),
migrations.AlterField(
model_name='membershipcommittee',
name='halloween_party',
model_name="membershipcommittee",
name="halloween_party",
field=models.BooleanField(blank=True, default=False, null=True),
),
migrations.AlterField(
model_name='membershipcommittee',
name='ice_cream_social',
model_name="membershipcommittee",
name="ice_cream_social",
field=models.BooleanField(blank=True, default=False, null=True),
),
migrations.AlterField(
model_name='membershipcommittee',
name='no_preference',
model_name="membershipcommittee",
name="no_preference",
field=models.BooleanField(blank=True, default=False, null=True),
),
migrations.AlterField(
model_name='membershipcommittee',
name='phone_directory',
model_name="membershipcommittee",
name="phone_directory",
field=models.BooleanField(blank=True, default=False, null=True),
),
migrations.AlterField(
model_name='membershipcommittee',
name='santa_visit',
model_name="membershipcommittee",
name="santa_visit",
field=models.BooleanField(blank=True, default=False, null=True),
),
migrations.AlterField(
model_name='membershipcommittee',
name='spring_garage_sale',
model_name="membershipcommittee",
name="spring_garage_sale",
field=models.BooleanField(blank=True, default=False, null=True),
),
migrations.AlterField(
model_name='membershipcommittee',
name='website',
model_name="membershipcommittee",
name="website",
field=models.BooleanField(blank=True, default=False, null=True),
),
]

View File

@@ -7,13 +7,15 @@ import phonenumber_field.modelfields
class Migration(migrations.Migration):
dependencies = [
('schasite', '0003_alter_membershipcommittee_block_captain_and_more'),
("schasite", "0003_alter_membershipcommittee_block_captain_and_more"),
]
operations = [
migrations.AlterField(
model_name='membershipperson',
name='phone_number',
field=phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, region=None),
model_name="membershipperson",
name="phone_number",
field=phonenumber_field.modelfields.PhoneNumberField(
max_length=128, null=True, region=None
),
),
]

View File

@@ -7,23 +7,58 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('schasite', '0004_alter_membershipperson_phone_number'),
("schasite", "0004_alter_membershipperson_phone_number"),
]
operations = [
migrations.CreateModel(
name='MembershipServices',
name="MembershipServices",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('babysitting', models.BooleanField(blank=True, default=False, null=True)),
('lawn_mowing', models.BooleanField(blank=True, default=False, null=True)),
('snow_shoveling', models.BooleanField(blank=True, default=False, null=True)),
('leaf_raking', models.BooleanField(blank=True, default=False, null=True)),
('petsitting', models.BooleanField(blank=True, default=False, null=True)),
('house_sitting', models.BooleanField(blank=True, default=False, null=True)),
('other', models.BooleanField(blank=True, default=False, null=True)),
('other_desc', models.CharField(blank=True, default='', max_length=256, null=True)),
('membership', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='schasite.membership')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"babysitting",
models.BooleanField(blank=True, default=False, null=True),
),
(
"lawn_mowing",
models.BooleanField(blank=True, default=False, null=True),
),
(
"snow_shoveling",
models.BooleanField(blank=True, default=False, null=True),
),
(
"leaf_raking",
models.BooleanField(blank=True, default=False, null=True),
),
(
"petsitting",
models.BooleanField(blank=True, default=False, null=True),
),
(
"house_sitting",
models.BooleanField(blank=True, default=False, null=True),
),
("other", models.BooleanField(blank=True, default=False, null=True)),
(
"other_desc",
models.CharField(blank=True, default="", max_length=256, null=True),
),
(
"membership",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
to="schasite.membership",
),
),
],
),
]

View File

@@ -7,31 +7,41 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('schasite', '0005_membershipservices'),
("schasite", "0005_membershipservices"),
]
operations = [
migrations.CreateModel(
name='CalendarEventAddressModel',
name="CalendarEventAddressModel",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('address_1', models.CharField(max_length=128)),
('address_2', models.CharField(blank=True, max_length=128)),
('city', models.CharField(max_length=128)),
('state', models.CharField(max_length=2)),
('zip_code', models.CharField(max_length=5)),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("address_1", models.CharField(max_length=128)),
("address_2", models.CharField(blank=True, max_length=128)),
("city", models.CharField(max_length=128)),
("state", models.CharField(max_length=2)),
("zip_code", models.CharField(max_length=5)),
],
),
migrations.RemoveField(
model_name='calendarevent',
name='location_address',
model_name="calendarevent",
name="location_address",
),
migrations.DeleteModel(
name='AddressModel',
name="AddressModel",
),
migrations.AddField(
model_name='calendareventaddressmodel',
name='calendar_event',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='schasite.calendarevent'),
model_name="calendareventaddressmodel",
name="calendar_event",
field=models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE, to="schasite.calendarevent"
),
),
]

View File

@@ -8,18 +8,39 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('schasite', '0006_calendareventaddressmodel_and_more'),
("schasite", "0006_calendareventaddressmodel_and_more"),
]
operations = [
migrations.CreateModel(
name='Payments',
name="Payments",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(default=datetime.datetime(2024, 3, 27, 17, 40, 28, 908036))),
('status', models.CharField(default='Completed', max_length=256)),
('email', models.EmailField(blank=True, max_length=254, null=True)),
('person', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='schasite.membershipperson')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"date",
models.DateField(
default=datetime.datetime(2024, 3, 27, 17, 40, 28, 908036)
),
),
("status", models.CharField(default="Completed", max_length=256)),
("email", models.EmailField(blank=True, max_length=254, null=True)),
(
"person",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="schasite.membershipperson",
),
),
],
),
]

View File

@@ -7,13 +7,17 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('schasite', '0007_payments'),
("schasite", "0007_payments"),
]
operations = [
migrations.AlterField(
model_name='payments',
name='date',
field=models.DateField(default=datetime.datetime(2024, 3, 27, 17, 41, 22, 742260, tzinfo=datetime.timezone.utc)),
model_name="payments",
name="date",
field=models.DateField(
default=datetime.datetime(
2024, 3, 27, 17, 41, 22, 742260, tzinfo=datetime.timezone.utc
)
),
),
]

View File

@@ -0,0 +1,40 @@
# Generated by Django 5.2 on 2025-04-14 15:33
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("schasite", "0008_alter_payments_date"),
]
operations = [
migrations.CreateModel(
name="SCHAOfficer",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("position", models.CharField(max_length=255)),
("email", models.EmailField(max_length=255)),
],
),
migrations.AlterField(
model_name="payments",
name="date",
field=models.DateField(
default=datetime.datetime(
2025, 4, 14, 15, 33, 4, 942154, tzinfo=datetime.timezone.utc
)
),
),
]

View File

@@ -3,7 +3,7 @@ from phonenumber_field.modelfields import PhoneNumberField
import datetime
from django.utils import timezone
# Create your models here.
class UsefulLinks(models.Model):
name = models.CharField(max_length=256)
@@ -22,7 +22,10 @@ class Membership(models.Model):
return self.addressmodel1.address_1
def get_person_1(self):
emails = [item.email for item in MembershipPerson.objects.filter(membership_id=self.id)]
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:
@@ -33,14 +36,14 @@ class Membership(models.Model):
def __str__(self):
return self.get_address_str() + " | " + self.get_person_1()
class AddressModel1(models.Model):
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)
zip_code = models.CharField(max_length=5)
class CalendarEvent(models.Model):
@@ -51,38 +54,45 @@ class CalendarEvent(models.Model):
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()
class CalendarEventAddressModel(models.Model):
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)
zip_code = models.CharField(max_length=5)
def __str__(self):
return (
self.address_1 + ", " + self.city + ", " + self.state + " " + self.zip_code
)
def __str__ (self):
return self.address_1 + ", " + self.city + ", " + self.state + " " + self.zip_code
class MembershipPerson(models.Model):
membership = models.ForeignKey(Membership, on_delete=models.CASCADE, blank=True, null=True)
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)
@@ -92,8 +102,6 @@ class MembershipPerson(models.Model):
return self.email if self.email else "No email"
class MembershipCommittee(models.Model):
membership = models.OneToOneField(Membership, on_delete=models.CASCADE)
block_captain = models.BooleanField(default=False, blank=True, null=True)
@@ -110,6 +118,7 @@ class MembershipCommittee(models.Model):
phone_directory = models.BooleanField(default=False, blank=True, null=True)
no_preference = models.BooleanField(default=False, blank=True, null=True)
class MembershipServices(models.Model):
membership = models.OneToOneField(Membership, on_delete=models.CASCADE)
babysitting = models.BooleanField(default=False, blank=True, null=True)
@@ -121,8 +130,36 @@ class MembershipServices(models.Model):
other = models.BooleanField(default=False, blank=True, null=True)
other_desc = models.CharField(default="", blank=True, null=True, max_length=256)
class Payments(models.Model):
date = models.DateField(default=timezone.now())
status = models.CharField(default='Completed', max_length=256)
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)
person = models.ForeignKey(
MembershipPerson, on_delete=models.CASCADE, blank=True, null=True
)
class SCHAOfficer(models.Model):
name = models.CharField(max_length=255)
position = models.CharField(max_length=255)
email = models.EmailField(max_length=255)
# class CommunitySchools(models.Model):
# 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(models.Model):
# name = models.CharField(max_length=255)
# distance = models.DecimalField(max_digits=3, decimal_places=1)
# description = models.CharField(max_length=1024)
# class CommunityParks(models.Model):
# name = models.CharField(max_length=255)
# distance = models.DecimalField(max_digits=3, decimal_places=1)
# description = models.CharField(max_length=1024)

View File

@@ -0,0 +1,47 @@
/* Custom Styles */
:root {
--bs-success-rgb: 40, 167, 69;
}
body {
padding-top: 56px;
}
section {
scroll-margin-top: 70px;
}
/* Hero Section */
#home {
background-color: rgba(var(--bs-success-rgb), 0.1);
}
/* News and Contact Sections */
.bg-success-10 {
background-color: rgba(var(--bs-success-rgb), 0.1);
}
/* Card hover effect */
.card {
transition: transform 0.2s ease-in-out;
}
.card:hover {
transform: translateY(-5px);
}
/* Contact icons */
.bi {
vertical-align: -.125em;
}
/* Form validation */
.was-validated .form-control:invalid,
.form-control.is-invalid {
border-color: #dc3545;
}
.was-validated .form-control:valid,
.form-control.is-valid {
border-color: #198754;
}

View File

@@ -0,0 +1,276 @@
{% extends 'schasite/base2.html' %}
{% load static %}
{% block pagetitle %}
<title>Stonehedge Community Homeowners Association</title>
{% endblock %}
{% block content %}
<section id="schools" class="py-5">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-6 mb-4 mb-lg-0">
<img src="../images/schools.jpg" alt="Local Schools" class="img-fluid rounded shadow">
</div>
<div class="col-lg-6">
<h2 class="text-success mb-4">Excellent Schools Serving Our Community</h2>
<p>Stonehedge Community Homeowners Association is proud to be served by some of the highest-rated schools in the district, providing quality education for all ages.</p>
<div class="accordion mt-4" id="schoolsAccordion">
<div class="accordion-item">
<h3 class="accordion-header" id="headingOne">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne">
Whittier Elementary School
</button>
</h3>
<div id="collapseOne" class="accordion-collapse collapse show" data-bs-parent="#schoolsAccordion">
<div class="accordion-body">
<p><strong>Rating:</strong> 87.5/100 (<a href="https://www.usnews.com/education/k12/illinois/whittier-elementary-school-249318">U.S. News</a>)<br>
<strong>Grades:</strong> K-5<br>
<strong>Enrollment:</strong> 414<br>
<strong>Distance:</strong> 2.3 miles from community entrance<br>
<strong>Principal:</strong> Robert Cerny</p>
<a href="https://www.cusd200.org/o/whittier" target="_blank" class="btn btn-sm btn-outline-success">Visit Website</a>
</div>
</div>
</div>
<div class="accordion-item">
<h3 class="accordion-header" id="headingTwo">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo">
Edison Middle School
</button>
</h3>
<div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#schoolsAccordion">
<div class="accordion-body">
<p><strong>Rating:</strong> 75.69/10 (<a href="https://www.usnews.com/education/k12/illinois/edison-middle-school-262446">U.S. News</a>)<br>
<strong>Grades:</strong> 6-8<br>
<strong>Enrollment:</strong> 674<br>
<strong>Distance:</strong> 2.5 miles from community entrance<br>
<strong>Principal:</strong> Rachel Bednar</p>
<a href="https://www.cusd200.org/o/edison" target="_blank" class="btn btn-sm btn-outline-success">Visit Website</a>
</div>
</div>
</div>
<div class="accordion-item">
<h3 class="accordion-header" id="headingThree">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree">
Wheaton Warrenville South High School
</button>
</h3>
<div id="collapseThree" class="accordion-collapse collapse" data-bs-parent="#schoolsAccordion">
<div class="accordion-body">
<p><strong>Rating:</strong> 90.43/10 (<a href="https://www.usnews.com/education/best-high-schools/illinois/districts/community-unified-school-district-200/wheaton-warrenville-south-high-school-7074">U.S. News</a>)<br>
<strong>Grades:</strong> 9-12<br>
<strong>Enrollment:</strong> 1845<br>
<strong>Distance:</strong> 3.1 miles from community entrance<br>
<strong>Principal:</strong> Lorie Campos</p>
<a href="https://www.cusd200.org/o/wheatonwarrenvillesouth" target="_blank" class="btn btn-sm btn-outline-success">Visit Website</a>
</div>
</div>
</div>
<div class="accordion-item">
<h3 class="accordion-header" id="headingFour">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFour">
St. Michael Parish School
</button>
</h3>
<div id="collapseFour" class="accordion-collapse collapse" data-bs-parent="#schoolsAccordion">
<div class="accordion-body">
<strong>Grades:</strong> Pre-K-8<br>
<strong>Distance:</strong> 2.5 miles from community entrance<br>
<strong>Principal:</strong> Adam Furgson</p>
<a href="https://www.stmichaelschoolwheaton.org/" target="_blank" class="btn btn-sm btn-outline-success">Visit Website</a>
</div>
</div>
</div>
<div class="accordion-item">
<h3 class="accordion-header" id="headingFive">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFive">
St. Francis High School
</button>
</h3>
<div id="collapseFive" class="accordion-collapse collapse" data-bs-parent="#schoolsAccordion">
<div class="accordion-body">
<strong>Grades:</strong> 9-12<br>
<strong>Enrollment:</strong> 698<br>
<strong>Distance:</strong> 2.7 miles from community entrance<br>
<strong>Principal:</strong> TBD</p>
<a href="https://www.sfhscollegeprep.org/" target="_blank" class="btn btn-sm btn-outline-success">Visit Website</a>
</div>
</div>
</div>
<div class="accordion-item">
<h3 class="accordion-header" id="headingSix">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSix">
Benet Academy High School
</button>
</h3>
<div id="collapseSix" class="accordion-collapse collapse" data-bs-parent="#schoolsAccordion">
<div class="accordion-body">
<strong>Grades:</strong> 9-12<br>
<strong>Enrollment:</strong> 1265<br>
<strong>Distance:</strong> 5.9 miles from community entrance<br>
<strong>Principal:</strong> TBD</p>
<a href="http://www.cusd200.org/Domain/140" target="_blank" class="btn btn-sm btn-outline-success">Visit Website</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- History Section -->
<section id="history" class="py-5 bg-success bg-opacity-10">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-6 order-lg-2 mb-4 mb-lg-0">
<img src="../images/history.jpg" alt="Community History" class="img-fluid rounded shadow">
</div>
<div class="col-lg-6 order-lg-1">
<h2 class="text-success mb-4">Our Rich History</h2>
<div class="timeline mt-4">
<div class="timeline-item">
<div class="timeline-badge bg-success"></div>
<div class="timeline-panel">
<div class="timeline-heading">
<h4 class="timeline-title">1977</h4>
</div>
<div class="timeline-body">
<p>Levitt Homes began building the Stonehedge Subdivision.&nbsp; In 1978, Levitt had financial problems and construction was halted for many months until local builders Joe Keim, Ed Keim and Faganel Builders took over the development of the Stonehedge subdivision in South Wheaton. Today these attractive residences are much in demand. The Orchard Cove subdivision was established in 1985 and development began in the Marywood subdivision in 1994.&nbsp; Both Orchard Cove &amp; Marywood were built by Keim.&nbsp; </p>
<p>hese subdivisions offer a variety of spacious, two-story Georgian, Tudor, Victorian and salt box style homes, mixed with a limited number of contemporaries, split levels and ranches.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Local Shopping Section -->
<section id="shopping" class="py-5">
<div class="container">
<h2 class="text-center text-success mb-5">Local Shopping & Dining</h2>
<p class="text-center mb-5">Stonehedge Community Homeowners Association is conveniently located near excellent shopping and dining options to meet all your needs.</p>
<div class="row g-4">
<div class="col-md-6">
<div class="card h-100 shadow-sm">
<img src="../images/greenwood-mall.jpg" class="card-img-top" alt="Greenwood Mall">
<div class="card-body">
<h5 class="card-title">Danada</h5>
<p class="card-text"><i class="bi bi-geo-alt-fill text-success me-2"></i>1.0 miles from community</p>
<p class="card-text">Over 10 stores including major department stores, fashion retailers, and specialty shops.</p>
</div>
<div class="card-footer bg-transparent">
<small class="text-muted">Open Mon-Sat 10am-9pm, Sun 11am-7pm</small>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100 shadow-sm">
<img src="../images/farmers-market.jpg" class="card-img-top" alt="Farmers Market">
<div class="card-body">
<h5 class="card-title">Downtown Wheaton</h5>
<p class="card-text"><i class="bi bi-geo-alt-fill text-success me-2"></i>2.7 miles from community</p>
<p class="card-text">Fresh local produce, artisanal foods, and handmade goods every Saturday morning.</p>
</div>
<div class="card-footer bg-transparent">
<small class="text-muted">Saturdays 7am-1pm, May through October</small>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100 shadow-sm">
<img src="../images/farmers-market.jpg" class="card-img-top" alt="Farmers Market">
<div class="card-body">
<h5 class="card-title">Wheaton Farmersmarket</h5>
<p class="card-text"><i class="bi bi-geo-alt-fill text-success me-2"></i>2.8 miles from community</p>
<p class="card-text">Fresh local produce, artisanal foods, and handmade goods every Saturday morning.</p>
</div>
<div class="card-footer bg-transparent">
<small class="text-muted">Saturdays 7am-1pm, May through October</small>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100 shadow-sm">
<img src="../images/dining-district.jpg" class="card-img-top" alt="Dining District">
<div class="card-body">
<h5 class="card-title">Downtown Naperville</h5>
<p class="card-text"><i class="bi bi-geo-alt-fill text-success me-2"></i>6.2 miles from community</p>
<p class="card-text">Diverse restaurants featuring Italian, Asian, Mexican, and American cuisine options.</p>
</div>
<div class="card-footer bg-transparent">
<small class="text-muted">Various hours - over 25 restaurants</small>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Parks & Recreation Section -->
<section id="parks" class="py-5 bg-success bg-opacity-10">
<div class="container">
<h2 class="text-center text-success mb-5">Parks</h2>
<div class="row g-4">
<div class="col-lg-6">
<div class="card shadow-sm h-100">
<div class="row g-0 h-100">
<div class="col-md-5">
<img src="../images/community-park.jpg" class="img-fluid rounded-start h-100" alt="Community Park" style="object-fit: cover;">
</div>
<div class="col-md-7">
<div class="card-body">
<h5 class="card-title">Brighton Park</h5>
<p class="card-text"><i class="bi bi-geo-alt-fill text-success me-2"></i>Within SCHA</p>
<ul class="list-group list-group-flush mb-3">
<li class="list-group-item"><i class="bi bi-check text-success me-2"></i>Playground</li>
<li class="list-group-item"><i class="bi bi-check text-success me-2"></i>Picninc Tables</li>
<li class="list-group-item"><i class="bi bi-check text-success me-2"></i>Dogs Allowed</li>
</ul>
<p class="card-text"><small class="text-muted">Open daily from dawn to dusk</small></p>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card shadow-sm h-100">
<div class="row g-0 h-100">
<div class="col-md-5">
<img src="../images/nature-preserve.jpg" class="img-fluid rounded-start h-100" alt="Nature Preserve" style="object-fit: cover;">
</div>
<div class="col-md-7">
<div class="card-body">
<h5 class="card-title">Seven Gables Park</h5>
<p class="card-text"><i class="bi bi-geo-alt-fill text-success me-2"></i>0.5 miles from SCHA</p>
<p>66.5 acres featuring sports fields, tennis courts & an ice rink, plus other recreational facilities.</p>
<ul class="list-group list-group-flush mb-3">
<li class="list-group-item"><i class="bi bi-check text-success me-2"></i>Playground</li>
<li class="list-group-item"><i class="bi bi-check text-success me-2"></i>Baseball Field</li>
<li class="list-group-item"><i class="bi bi-check text-success me-2"></i>Tennis Court</li>
<li class="list-group-item"><i class="bi bi-check text-success me-2"></i>BasketballCourt</li>
<li class="list-group-item"><i class="bi bi-check text-success me-2"></i>Picninc Tables</li>
<li class="list-group-item"><i class="bi bi-check text-success me-2"></i>Dogs Allowed</li>
</ul>
<p class="card-text"><small class="text-muted">Open daily from 8am to sunset</small></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,90 @@
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% block pagetitle %}
{% endblock %}
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<!-- Custom CSS -->
<link href="{% static 'css/style2.css' %}" rel="stylesheet" type="text/css" />
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-success sticky-top">
<div class="container">
<a class="navbar-brand" href="{% url 'index2' %}">Stonehedge Community Homeowners Association</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'index2' %}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'about_us2' %}">About Us</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'calendar2' %}">Calendar</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'dues2' %}">Pay Dues</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'membership_form2' %}">Join Today</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'scha_board2' %}">SCHA Board</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'useful_links2' %}">Useful Links</a>
</li>
</ul>
</div>
</div>
</nav>
{% block content %}
{% endblock %}
<!-- Footer -->
<footer class="bg-dark text-white pt-5 pb-4">
<div class="container">
<div class="row">
<div class="col-lg-6 mb-4 mb-lg-0">
<h5 class="text-uppercase mb-4">Stonehedge Community Homeowners Association</h5>
<p class="text-white-50">1234 Greenwood Lane<br>Anytown, ST 12345</p>
</div>
<div class="col-lg-4 offset-lg-2 mb-4 mb-lg-0">
<h5 class="text-uppercase mb-4">Quick Links</h5>
<ul class="list-unstyled">
<li class="mb-2"><a href="{% url 'index2' %}" class="text-white-50 text-decoration-none">Home</a></li>
<li class="mb-2"><a href="{% url 'about_us2' %}" class="text-white-50 text-decoration-none">About</a></li>
<li class="mb-2"><a href="{% url 'calendar2' %}" class="text-white-50 text-decoration-none">Calendar</a></li>
<li class="mb-2"><a href="{% url 'dues2' %}" class="text-white-50 text-decoration-none">Dues</a></li>
<li class="mb-2"><a href="{% url 'membership_form2' %}" class="text-white-50 text-decoration-none">Membership Form</a></li>
<li class="mb-2"><a href="{% url 'scha_board2' %}" class="text-white-50 text-decoration-none">SCHA Board</a></li>
<li class="mb-2"><a href="{% url 'useful_links2' %}" class="text-white-50 text-decoration-none">Links</a></li>
</ul>
</div>
</div>
<hr class="my-4 text-white-50">
<div class="row align-items-center">
<div id="footer">&copy; Stonehedge Community Homeowners Association 2010-<script>document.write( new Date().getFullYear() );</script>. All rights reserved
</div>
</div>
</footer>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Custom JS -->
{% block extra_js %}
{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,202 @@
{% extends 'schasite/base2.html' %}
{% load static %}
{% block pagetitle %}
<title>Stonehedge Community Homeowners Association</title>
<style type="text/css">
/* Calendar Page Specific Styles */
.event-card {
transition: transform 0.3s ease;
margin-bottom: 1.5rem;
}
.event-card:hover {
transform: translateY(-5px);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
.event-card .card-header {
padding: 0.75rem 1.25rem;
}
.event-card img {
height: 180px;
width: 100%;
object-fit: cover;
}
.nav-tabs .nav-link {
color: var(--bs-success);
font-weight: 500;
}
.nav-tabs .nav-link.active {
color: var(--bs-success);
border-color: var(--bs-success);
font-weight: 600;
}
/* Responsive adjustments */
@media (max-width: 767.98px) {
.event-card img {
height: 120px;
}
.event-card .col-md-4 {
margin-bottom: 1rem;
}
}
/* Icon alignment */
.bi-calendar-event,
.bi-calendar-range,
.bi-clock,
.bi-geo-alt,
.bi-house,
.bi-person,
.bi-envelope {
width: 1.25rem;
text-align: center;
}
</style>
{% endblock %}
{% block content %}
<main class="py-5">
<div class="container">
<!-- Calendar Navigation -->
<div class="row mb-4">
<div class="col-12">
<ul class="nav nav-tabs" id="calendarTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="upcoming-tab" data-bs-toggle="tab" data-bs-target="#upcoming" type="button" role="tab">
Upcoming Events
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="past-tab" data-bs-toggle="tab" data-bs-target="#past" type="button" role="tab">
Past Events
</button>
</li>
</ul>
</div>
</div>
<!-- Calendar Content -->
<div class="tab-content" id="calendarTabContent">
<!-- Upcoming Events Tab -->
<div class="tab-pane fade show active" id="upcoming" role="tabpanel">
<div class="row g-4">
{% for event in future_events %}
<div class="col-lg-6">
<div class="card h-100 shadow-sm">
<div class="card-header bg-success text-white">
<div class="d-flex justify-content-between align-items-center">
<h3 class="h5 mb-0">{{ event.event_name }}</h3>
<span class="badge bg-light text-success">Upcoming</span>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<img src="../images/events/picnic.jpg" alt="Community Picnic" class="img-fluid rounded mb-3 mb-md-0">
</div>
<div class="col-md-8">
<div class="mb-3">
<p class="mb-1"><i class="bi bi-calendar-event text-success me-2"></i><strong>Date:</strong> {{ event.start_date }}</p>
<p class="mb-1"><i class="bi bi-clock text-success me-2"></i><strong>Time:</strong> 11:00 AM - 3:00 PM</p>
<p class="mb-1"><i class="bi bi-geo-alt text-success me-2"></i><strong>Location:</strong> {{ event.location_name }}</p>
<p class="mb-1"><i class="bi bi-house text-success me-2"></i><strong>Address:</strong> </p>
<p class="mb-1"><i class="bi bi-person text-success me-2"></i><strong>Coordinator:</strong> {{ event.coordinator_name }}</p>
<p class="mb-1"><i class="bi bi-envelope text-success me-2"></i><strong>Email:</strong> <a href="mailto:{{ event.coordinator_email }}">{{ event.coordinator_email }}</a></p>
</div>
<p>{{ event.description }}</p>
<div class="d-flex justify-content-between align-items-center">
<a href="{{ event.event_url }}" class="btn btn-sm btn-success">{{ event.event_link_name }}</a>
{% if event.rsvp_date %}
<small class="text-muted">RSVP by {{ event.rsvp_date }}</small>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Past Events Tab -->
<div class="tab-pane fade" id="past" role="tabpanel">
<div class="row g-4">
<!-- Past Event 1 -->
{% for event in future_events %}
<div class="col-lg-6">
<div class="card h-100 shadow-sm">
<div class="card-header bg-secondary text-white">
<div class="d-flex justify-content-between align-items-center">
<h3 class="h5 mb-0">{{ event.event_name }}</h3>
<span class="badge bg-light text-secondary">Completed</span>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<img src="../images/events/spring-cleanup.jpg" alt="Spring Cleanup" class="img-fluid rounded mb-3 mb-md-0">
</div>
<div class="col-md-8">
<div class="mb-3">
<p class="mb-1"><i class="bi bi-calendar-event text-secondary me-2"></i><strong>Date:</strong> {{ event.start_date }}</p>
<p class="mb-1"><i class="bi bi-clock text-secondary me-2"></i><strong>Time:</strong> 9:00 AM - 12:00 PM</p>
<p class="mb-1"><i class="bi bi-geo-alt text-secondary me-2"></i><strong>Location:</strong> {{ event.location_name }}</p>
<p class="mb-1"><i class="bi bi-house text-secondary me-2"></i><strong>Address:</strong> </p>
<p class="mb-1"><i class="bi bi-person text-secondary me-2"></i><strong>Coordinator:</strong> {{ event.coordinator_name }}</p>
<p class="mb-1"><i class="bi bi-envelope text-secondary me-2"></i><strong>Email:</strong> <a href="mailto:{{ event.coordinator_email }}">{{ event.coordinator_email }}</a></p>
</div>
<p>{{ event.description }}</p>
<div class="d-flex justify-content-between align-items-center">
<a href="https://greenwoodestates.org/events/spring-cleanup-2024" class="btn btn-sm btn-secondary">Event Recap</a>
<small class="text-muted">52 volunteers participated</small>
</div>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</main>
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// This would be replaced with actual event loading from a database
console.log('Calendar page loaded');
// Example: Highlight current tab in URL
const urlParams = new URLSearchParams(window.location.search);
const tab = urlParams.get('tab');
if (tab === 'past') {
const pastTab = new bootstrap.Tab(document.getElementById('past-tab'));
pastTab.show();
}
// Example: Add event to Google Calendar
document.querySelectorAll('.add-to-calendar').forEach(button => {
button.addEventListener('click', function() {
// In a real implementation, this would generate a calendar event
alert('This would add the event to your calendar');
});
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,230 @@
{% extends 'schasite/base2.html' %}
{% load static %}
{% block pagetitle %}
<title>Stonehedge Community Homeowners Association</title>
<style type="text/css">
/* Payment Page Specific Styles */
.payment-option-card {
transition: transform 0.3s ease;
border-width: 2px;
}
.payment-option-card:hover {
transform: translateY(-5px);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
.payment-option-card .bi {
font-size: 3rem;
}
/* Table styles */
.table thead th {
background-color: var(--bs-success);
color: white;
}
/* Responsive adjustments */
@media (max-width: 767.98px) {
.payment-option-card {
margin-bottom: 1.5rem;
}
.table-responsive {
font-size: 0.875rem;
}
}
/* Stripe button styling */
.btn-stripe {
background-color: #635bff;
color: white;
border: none;
}
.btn-stripe:hover {
background-color: #4a42d1;
color: white;
}
</style>
{% endblock %}
{% block content %}
<main class="py-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8">
<!-- Payment Information Card -->
<div class="card shadow-sm mb-5">
<div class="card-header bg-success text-white">
<h3 class="mb-0">Payment Information</h3>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="bi bi-info-circle-fill me-2"></i>
You will be redirected to our secure payment processor (Stripe) to complete your transaction.
</div>
<h4 class="text-success mb-4">Annual Dues</h4>
<div class="table-responsive mb-4">
<table class="table table-bordered">
<thead class="table-success">
<tr>
<th>Payment Option</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td>Full Payment (Annual)</td>
<td>$30.00</td>
</tr>
</tbody>
</table>
</div>
<!-- <div class="mb-4 p-3 bg-light rounded">
<h5 class="text-success"><i class="bi bi-house-check-fill me-2"></i>Property Information</h5>
<p><strong>Member:</strong> John Doe (Lot #42)</p>
<p><strong>Current Balance:</strong> $600.00 (Due January 15, 2024)</p>
<p><strong>Last Payment:</strong> $600.00 on January 10, 2023</p>
<a href="#" class="btn btn-sm btn-outline-success">View Payment History</a>
</div> -->
</div>
</div>
<!-- Payment Options -->
<div class="card shadow-sm mb-5">
<div class="card-header bg-success text-white">
<h3 class="mb-0">Payment Options</h3>
</div>
<div class="card-body">
<div class="row g-4">
<!-- Credit/Debit Card -->
<div class="col-md-6">
<div class="card h-100 border-success">
<div class="card-body text-center">
<i class="bi bi-credit-card text-success display-4 mb-3"></i>
<h4 class="text-success">Credit/Debit Card</h4>
<p class="card-text">Pay securely with Visa, Mastercard, American Express, or Discover.</p>
<a href="https://buy.stripe.com/test_14k6rE7jD3hQ2SQ144" class="btn btn-success mt-3" target="_blank" id="submitBtn">
Pay with Card
</a>
</div>
</div>
</div>
</div>
<hr class="my-4">
<!-- Other Payment Methods -->
<div class="row">
<div class="col-12">
<h5 class="text-success mb-3">Other Payment Methods</h5>
<div class="d-flex flex-wrap gap-3">
<a href="#" class="btn btn-outline-success">
<i class="bi bi-cash-coin me-2"></i>Mail a Check
</a>
<a href="#" class="btn btn-outline-success">
<i class="bi bi-building me-2"></i>In-Person Payment
</a>
</div>
</div>
</div>
</div>
</div>
<!-- Payment FAQ -->
<div class="card shadow-sm">
<div class="card-header bg-success text-white">
<h3 class="mb-0">Payment FAQ</h3>
</div>
<div class="card-body">
<div class="accordion" id="paymentFAQ">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#faq1">
Is there a fee for online payments?
</button>
</h2>
<div id="faq1" class="accordion-collapse collapse show" data-bs-parent="#paymentFAQ">
<div class="accordion-body">
No, the HOA absorbs all processing fees for online payments. You pay exactly your dues amount with no additional fees.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faq2">
When will my payment be reflected in my account?
</button>
</h2>
<div id="faq2" class="accordion-collapse collapse" data-bs-parent="#paymentFAQ">
<div class="accordion-body">
Credit/debit card payments are posted immediately.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faq4">
What payment methods do you accept?
</button>
</h2>
<div id="faq4" class="accordion-collapse collapse" data-bs-parent="#paymentFAQ">
<div class="accordion-body">
Online we accept all major credit/debit cards (Visa, Mastercard, American Express, Discover). For in-person or mail payments, we accept checks or money orders made payable to "SCHA"
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Stripe Integration Script (example) -->
<script>
// This would be replaced with your actual Stripe integration code
document.addEventListener('DOMContentLoaded', function() {
// Example: Track outbound links to Stripe
const stripeLinks = document.querySelectorAll('a[href*="stripe.com"]');
stripeLinks.forEach(link => {
link.addEventListener('click', function(e) {
// You could add analytics tracking here
console.log('Redirecting to Stripe payment');
});
});
// Example: Dynamic balance loading (would connect to your backend)
function loadAccountBalance() {
// In a real implementation, this would fetch from your database
// This is just a placeholder example
return {
member: "John Doe (Lot #42)",
balance: "$600.00",
dueDate: "January 15, 2024",
lastPayment: "$600.00 on January 10, 2023"
};
}
// Update the account info section (example)
const accountInfo = loadAccountBalance();
document.querySelector('.bg-light p:nth-child(1)').innerHTML =
`<strong>Member:</strong> ${accountInfo.member}`;
document.querySelector('.bg-light p:nth-child(2)').innerHTML =
`<strong>Current Balance:</strong> ${accountInfo.balance} (Due ${accountInfo.dueDate})`;
document.querySelector('.bg-light p:nth-child(3)').innerHTML =
`<strong>Last Payment:</strong> ${accountInfo.lastPayment}`;
});
</script>
<script src="{% static 'main.js' %}"></script>
{% endblock %}

View File

@@ -0,0 +1,208 @@
{% extends 'schasite/base2.html' %}
{% load static %}
{% block pagetitle %}
<title>Stonehedge Community Homeowners Association</title>
{% endblock %}
{% block content %}
<!-- Hero Section -->
<section id="home" class="py-5 bg-success bg-opacity-10">
<div class="container py-5">
<div class="row align-items-center">
<div class="col-lg-8 mb-4 mb-lg-0">
<h1 class="display-4 fw-bold mb-3">Welcome to Stonehedge Community Homeowners Association</h1>
<p class="lead mb-4">A premier community dedicated to maintaining beautiful homes, safe neighborhoods, and a high quality of life for all residents.</p>
<a href="{% url 'about_us2' %}" class="btn btn-success btn-lg px-4">Learn More</a>
</div>
<div class="col-lg-4">
<img src='{% static "images/bricks.jpg" %}' alt="Stonehedge Community Homeowners Association" class="img-fluid rounded-circle shadow">
</div>
</div>
</div>
</section>
<!-- About Section -->
<section id="about" class="py-5">
<div class="container">
<h2 class="text-center text-success mb-5">About Our Community</h2>
<div class="row g-4">
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<img src="images/amenities.jpg" class="card-img-top" alt="Community Amenities" style="height: 200px; object-fit: cover;">
<div class="card-body">
<h5 class="card-title">Amenities</h5>
<p class="card-text">Our community features a swimming pool, playground, walking trails, and clubhouse available for resident use.</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<img src="images/board.jpg" class="card-img-top" alt="HOA Board" style="height: 200px; object-fit: cover;">
<div class="card-body">
<h5 class="card-title">HOA Board</h5>
<p class="card-text">Meet our elected board members who volunteer their time to maintain and improve our neighborhood.</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<img src="images/rules.jpg" class="card-img-top" alt="Community Rules" style="height: 200px; object-fit: cover;">
<div class="card-body">
<h5 class="card-title">Community Rules</h5>
<p class="card-text">Learn about our community guidelines designed to maintain property values and quality of life for all residents.</p>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- News Section -->
<section id="news" class="py-5 bg-success bg-opacity-10">
<div class="container">
<h2 class="text-center text-success mb-5">Community News & Events</h2>
<div class="row g-4">
<div class="col-md-6">
<div class="card h-100 shadow-sm">
<div class="card-body">
<h5 class="card-title">Annual Community Picnic</h5>
<p class="card-text">Join us on June 15th for our annual community picnic at the clubhouse. Food, games, and fun for the whole family!</p>
</div>
<div class="card-footer bg-transparent border-0">
<a href="#" class="btn btn-outline-success">RSVP Now</a>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100 shadow-sm">
<div class="card-body">
<h5 class="card-title">Landscaping Updates</h5>
<p class="card-text">New landscaping will be installed in common areas beginning next month. Expect temporary parking restrictions.</p>
</div>
<div class="card-footer bg-transparent border-0">
<a href="#" class="btn btn-outline-success">View Schedule</a>
</div>
</div>
</div>
</div>
<div class="text-center mt-4">
<a href="#" class="btn btn-success px-4">View All News</a>
</div>
</div>
</section>
<!-- Documents Section -->
<section id="documents" class="py-5">
<div class="container">
<h2 class="text-center text-success mb-5">Community Documents</h2>
<div class="row g-4">
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-header bg-success text-white">
<h5 class="mb-0">Governing Documents</h5>
</div>
<div class="list-group list-group-flush">
<a href="#" class="list-group-item list-group-item-action">CC&Rs</a>
<a href="#" class="list-group-item list-group-item-action">Bylaws</a>
<a href="#" class="list-group-item list-group-item-action">Articles of Incorporation</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-header bg-success text-white">
<h5 class="mb-0">Meeting Minutes</h5>
</div>
<div class="list-group list-group-flush">
<a href="#" class="list-group-item list-group-item-action">2023 Meetings</a>
<a href="#" class="list-group-item list-group-item-action">2022 Meetings</a>
<a href="#" class="list-group-item list-group-item-action">Archive</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-header bg-success text-white">
<h5 class="mb-0">Forms</h5>
</div>
<div class="list-group list-group-flush">
<a href="#" class="list-group-item list-group-item-action">Architectural Request</a>
<a href="#" class="list-group-item list-group-item-action">Complaint Form</a>
<a href="#" class="list-group-item list-group-item-action">Resale Certificate Request</a>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Contact Section -->
<!-- <section id="contact" class="py-5 bg-success bg-opacity-10">
<div class="container">
<h2 class="text-center text-success mb-5">Contact Us</h2>
<div class="row g-4 mb-5">
<div class="col-md-6">
<div class="card h-100 text-center shadow-sm">
<div class="card-body py-4">
<i class="bi bi-envelope-fill text-success fs-1 mb-3"></i>
<h5 class="card-title">Email Us</h5>
<p class="card-text">board@greenwoodestates.org</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100 text-center shadow-sm">
<div class="card-body py-4">
<i class="bi bi-telephone-fill text-success fs-1 mb-3"></i>
<h5 class="card-title">Call Us</h5>
<p class="card-text">(555) 123-4567</p>
<p class="card-text">Monday-Friday, 9am-5pm</p>
</div>
</div>
</div>
</div>
<div class="row justify-content-center">
<div class="col-lg-8">
<form class="needs-validation" novalidate>
<div class="row g-3">
<div class="col-md-6">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" required>
<div class="invalid-feedback">
Please provide your name.
</div>
</div>
<div class="col-md-6">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" required>
<div class="invalid-feedback">
Please provide a valid email.
</div>
</div>
<div class="col-12">
<label for="subject" class="form-label">Subject</label>
<input type="text" class="form-control" id="subject" required>
<div class="invalid-feedback">
Please provide a subject.
</div>
</div>
<div class="col-12">
<label for="message" class="form-label">Message</label>
<textarea class="form-control" id="message" rows="4" required></textarea>
<div class="invalid-feedback">
Please enter your message.
</div>
</div>
<div class="col-12 text-center">
<button class="btn btn-success btn-lg px-4" type="submit">
<i class="bi bi-send-fill me-2"></i>Send Message
</button>
</div>
</div>
</form>
</div>
</div>
</div> -->
</section>
{% endblock %}

View File

@@ -0,0 +1,414 @@
{% extends 'schasite/base2.html' %}
{% load static %}
{% block pagetitle %}
<title>Stonehedge Community Homeowners Association</title>
<script type="text/css">
/* Membership Form Specific Styles */
#membershipForm fieldset {
margin-bottom: 2rem;
padding: 1.5rem;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
}
#membershipForm legend {
width: auto;
padding: 0 0.5rem;
margin-bottom: 0;
font-size: 1.25rem;
}
.form-check-label {
user-select: none;
}
/* Responsive adjustments */
@media (max-width: 767.98px) {
#membershipForm fieldset {
padding: 1rem;
}
#membershipForm legend {
font-size: 1.1rem;
}
}
/* Form validation styling */
.was-validated .form-control:valid,
.was-validated .form-control.is-valid {
border-color: #198754;
background-image: none;
}
.was-validated .form-control:invalid,
.was-validated .form-control.is-invalid {
border-color: #dc3545;
background-image: none;
}
/* Modal styling */
.modal-content {
border: none;
}
.modal-header {
background-color: var(--bs-success);
color: white;
}
.modal-header .btn-close {
filter: invert(1);
}
</script>
{% endblock %}
{% block content %}
<main class="py-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card shadow-sm">
<div class="card-header bg-success text-white">
<h2 class="h4 mb-0">New Member Information</h2>
</div>
<div class="card-body">
<form id="membershipForm" class="needs-validation" novalidate>
<!-- Household Information -->
<fieldset class="mb-4">
<legend class="h5 text-success border-bottom pb-2">Household Information</legend>
<!-- Address Section -->
<div class="row g-3 mb-4">
<div class="col-md-6">
<label for="streetAddress" class="form-label">Street Address*</label>
<input type="text" class="form-control" id="streetAddress" required>
<div class="invalid-feedback">
Please provide your street address.
</div>
</div>
<div class="col-md-6">
<label for="unit" class="form-label">Unit/Apt #</label>
<input type="text" class="form-control" id="unit">
</div>
<div class="col-md-4">
<label for="city" class="form-label">City*</label>
<input type="text" class="form-control" id="city" required>
<div class="invalid-feedback">
Please provide your city.
</div>
</div>
<div class="col-md-4">
<label for="state" class="form-label">State*</label>
<select class="form-select" id="state" required>
<option value="" selected disabled>Choose...</option>
<option>Alabama</option>
<option>Alaska</option>
<!-- Add all states -->
<option>Wyoming</option>
</select>
<div class="invalid-feedback">
Please select your state.
</div>
</div>
<div class="col-md-4">
<label for="zipCode" class="form-label">ZIP Code*</label>
<input type="text" class="form-control" id="zipCode" required>
<div class="invalid-feedback">
Please provide your ZIP code.
</div>
</div>
</div>
<!-- Primary Member -->
<div class="row g-3 mb-4">
<div class="col-12">
<h5 class="text-success">Primary Member*</h5>
</div>
<div class="col-md-6">
<label for="firstName1" class="form-label">First Name*</label>
<input type="text" class="form-control" id="firstName1" required>
<div class="invalid-feedback">
Please provide first name.
</div>
</div>
<div class="col-md-6">
<label for="lastName1" class="form-label">Last Name*</label>
<input type="text" class="form-control" id="lastName1" required>
<div class="invalid-feedback">
Please provide last name.
</div>
</div>
<div class="col-md-6">
<label for="email1" class="form-label">Email*</label>
<input type="email" class="form-control" id="email1" required>
<div class="invalid-feedback">
Please provide a valid email.
</div>
</div>
<div class="col-md-6">
<label for="phone1" class="form-label">Phone*</label>
<input type="tel" class="form-control" id="phone1" required>
<div class="invalid-feedback">
Please provide phone number.
</div>
</div>
</div>
<!-- Secondary Member (Optional) -->
<div class="row g-3 mb-4">
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="addSecondMember">
<label class="form-check-label" for="addSecondMember">
Add Second Household Member
</label>
</div>
</div>
</div>
<div id="secondMemberSection" class="row g-3 mb-4" style="display: none;">
<div class="col-12">
<h5 class="text-success">Secondary Member</h5>
</div>
<div class="col-md-6">
<label for="firstName2" class="form-label">First Name</label>
<input type="text" class="form-control" id="firstName2">
</div>
<div class="col-md-6">
<label for="lastName2" class="form-label">Last Name</label>
<input type="text" class="form-control" id="lastName2">
</div>
<div class="col-md-6">
<label for="email2" class="form-label">Email</label>
<input type="email" class="form-control" id="email2">
</div>
<div class="col-md-6">
<label for="phone2" class="form-label">Phone</label>
<input type="tel" class="form-control" id="phone2">
</div>
</div>
</fieldset>
<!-- Committees Section -->
<fieldset class="mb-4">
<legend class="h5 text-success border-bottom pb-2">Committee Interests</legend>
<p>Check all committees you're interested in joining:</p>
<div class="row">
<div class="col-md-6">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="socialCommittee">
<label class="form-check-label" for="socialCommittee">
Social & Events Committee
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="landscapeCommittee">
<label class="form-check-label" for="landscapeCommittee">
Landscape & Maintenance Committee
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="safetyCommittee">
<label class="form-check-label" for="safetyCommittee">
Safety & Neighborhood Watch
</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="architecturalCommittee">
<label class="form-check-label" for="architecturalCommittee">
Architectural Review Committee
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="communicationsCommittee">
<label class="form-check-label" for="communicationsCommittee">
Communications Committee
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="welcomingCommittee">
<label class="form-check-label" for="welcomingCommittee">
Welcoming Committee
</label>
</div>
</div>
</div>
<div class="mt-3">
<label for="otherCommitteeInterest" class="form-label">Other interests or skills you'd like to contribute:</label>
<textarea class="form-control" id="otherCommitteeInterest" rows="2"></textarea>
</div>
</fieldset>
<!-- Services Section -->
<fieldset class="mb-4">
<legend class="h5 text-success border-bottom pb-2">Services</legend>
<p>Check if you're interested in providing these services to neighbors:</p>
<div class="row">
<div class="col-md-6">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="babysitting">
<label class="form-check-label" for="babysitting">
Babysitting
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="lawnMowing">
<label class="form-check-label" for="lawnMowing">
Lawn Mowing
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="snowShoveling">
<label class="form-check-label" for="snowShoveling">
Snow Shoveling
</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="houseSitting">
<label class="form-check-label" for="houseSitting">
House Sitting
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="petCare">
<label class="form-check-label" for="petCare">
Pet Care
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="otherService">
<label class="form-check-label" for="otherService">
Other (please specify)
</label>
</div>
</div>
</div>
<div class="mt-3" id="otherServiceSpecify" style="display: none;">
<label for="otherServiceDetails" class="form-label">Please specify other service:</label>
<input type="text" class="form-control" id="otherServiceDetails">
</div>
</fieldset>
<!-- Terms & Submit -->
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="agreeTerms" required>
<label class="form-check-label" for="agreeTerms">
I agree to the <a href="#" data-bs-toggle="modal" data-bs-target="#termsModal">Terms and Conditions</a>*
</label>
<div class="invalid-feedback">
You must agree before submitting.
</div>
</div>
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<button class="btn btn-outline-secondary me-md-2" type="reset">Reset Form</button>
<button class="btn btn-success" type="submit">Submit Membership</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- Terms Modal -->
<div class="modal fade" id="termsModal" tabindex="-1" aria-labelledby="termsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="termsModalLabel">Membership Terms and Conditions</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<h6>Greenwood Estates HOA Membership Agreement</h6>
<p>By joining the Greenwood Estates Homeowners Association, you agree to:</p>
<ol>
<li>Abide by the community covenants, conditions, and restrictions (CC&Rs)</li>
<li>Pay annual membership dues in a timely manner</li>
<li>Participate in community standards and guidelines</li>
<li>Receive communications from the HOA board and committees</li>
<li>Have your contact information shared with other members for community purposes</li>
</ol>
<p>Your membership helps maintain our community's quality and property values. The HOA board reserves the right to approve or deny membership applications.</p>
<h6>Privacy Policy</h6>
<p>Your personal information will be used solely for HOA communications and operations. We do not sell or share member information with third parties except as required for community management.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" data-bs-dismiss="modal">I Understand</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Toggle second member section
const addSecondMember = document.getElementById('addSecondMember');
const secondMemberSection = document.getElementById('secondMemberSection');
addSecondMember.addEventListener('change', function() {
if(this.checked) {
secondMemberSection.style.display = 'flex';
} else {
secondMemberSection.style.display = 'none';
// Clear second member fields when hidden
document.getElementById('firstName2').value = '';
document.getElementById('lastName2').value = '';
document.getElementById('email2').value = '';
document.getElementById('phone2').value = '';
}
});
// Toggle other service field
const otherService = document.getElementById('otherService');
const otherServiceSpecify = document.getElementById('otherServiceSpecify');
otherService.addEventListener('change', function() {
otherServiceSpecify.style.display = this.checked ? 'block' : 'none';
if(!this.checked) {
document.getElementById('otherServiceDetails').value = '';
}
});
// Form validation
const form = document.getElementById('membershipForm');
form.addEventListener('submit', function(event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add('was-validated');
}, false);
// Phone number formatting
const phoneInputs = document.querySelectorAll('input[type="tel"]');
phoneInputs.forEach(input => {
input.addEventListener('input', function() {
this.value = this.value.replace(/[^0-9]/g, '');
if(this.value.length > 10) {
this.value = this.value.slice(0, 10);
}
});
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,9 @@
{% extends 'schasite/base2.html' %}
{% load static %}
{% block pagetitle %}
<title>Stonehedge Community Homeowners Association</title>
{% endblock %}
{% block content %}
{% endblock %}

View File

@@ -0,0 +1,149 @@
{% extends 'schasite/base2.html' %}
{% load static %}
{% block pagetitle %}
<title>Stonehedge Community Homeowners Association</title>
<style type="text/css">
/* Board Page Specific Styles */
.card-header h4 {
font-size: 1.25rem;
}
.board-member-img {
width: 150px;
height: 150px;
object-fit: cover;
border: 3px solid var(--bs-success);
}
.committee-card {
transition: transform 0.3s ease;
}
.committee-card:hover {
transform: translateY(-5px);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
/* Responsive adjustments */
@media (max-width: 767.98px) {
.board-member-img {
width: 120px;
height: 120px;
}
}
</style>
{% endblock %}
{% block content %}
<main class="py-5">
<div class="container">
<!-- Board Information -->
<div class="row justify-content-center mb-5">
<div class="col-lg-10">
<div class="card shadow-sm">
<div class="card-body text-center">
<h3 class="text-success mb-3">2025-2026 Stonehedge Community Homeowners Association Board</h3>
<p>The Stonehedge Community Homeowners Association is governed by a volunteer board of directors elected by the community members. Board members serve two-year terms and are responsible for overseeing the community's operations, finances, and enforcement of covenants.</p>
<p>Board meetings are held monthly and are open to all residents. <a href="../index.html#documents">View meeting schedule</a>.</p>
</div>
</div>
</div>
</div>
<!-- Board Members -->
<div class="row g-4">
{% for officer in officers %}
{% if officer %}
<div class="col-md-6 col-lg-4">
<div class="card h-100 shadow-sm">
<div class="card-header bg-success text-white text-center py-3">
<h4 class="mb-0">{{ officer.position }}</h4>
</div>
<div class="card-body text-center">
<img src="../images/board/john-smith.jpg" alt="John Smith" class="rounded-circle mb-3" width="150" height="150">
<h5 class="card-title">{{ officer.name }}</h5>
<!-- <p class="text-muted">Term: 2022-2024</p>
<p class="card-text">John has lived in Greenwood Estates since 2015 and brings 20 years of financial management experience to the board.</p> -->
<div class="d-flex justify-content-center">
<a href="mailto:{{ officer.email }}" class="btn btn-sm btn-outline-success me-2">
<i class="bi bi-envelope"></i> Email
</a>
<!-- <a href="tel:+15551234567" class="btn btn-sm btn-outline-success">
<i class="bi bi-telephone"></i> Call
</a> -->
</div>
</div>
</div>
</div>
{% else %}
<div class="col-md-6 col-lg-4">
<div class="card h-100 shadow-sm">
<div class="card-header bg-success text-white text-center py-3">
<h4 class="mb-0">TBD</h4>
</div>
<div class="card-body text-center">
<img src="../images/board/john-smith.jpg" alt="John Smith" class="rounded-circle mb-3" width="150" height="150">
<h5 class="card-title">TBD</h5>
<p class="text-muted">TBD</p>
<p class="card-text">TBD</p>
</div>
</div>
</div>
{% endif %}
{% endfor %}
<!-- Committees Section -->
<div class="row mt-5">
<div class="col-12">
<div class="card shadow-sm">
<div class="card-header bg-success text-white">
<h3 class="mb-0">HOA Committees</h3>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title text-success">Architectural Review</h5>
<p class="card-text">Reviews all exterior modification requests to ensure compliance with community standards.</p>
<p><strong>Chair:</strong> Robert Wilson</p>
<a href="mailto:architecture@greenwoodestates.org" class="btn btn-sm btn-outline-success">Contact Committee</a>
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title text-success">Social & Events</h5>
<p class="card-text">Plans community events, holiday celebrations, and social gatherings.</p>
<p><strong>Chair:</strong> Lisa Martinez</p>
<a href="mailto:events@greenwoodestates.org" class="btn btn-sm btn-outline-success">Contact Committee</a>
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title text-success">Landscape & Maintenance</h5>
<p class="card-text">Oversees common area maintenance and landscaping improvements.</p>
<p><strong>Chair:</strong> David Thompson</p>
<a href="mailto:landscape@greenwoodestates.org" class="btn btn-sm btn-outline-success">Contact Committee</a>
</div>
</div>
</div>
</div>
<div class="text-center mt-3">
<a href="#" class="btn btn-success">Learn About Committee Opportunities</a>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
{% endblock %}

View File

@@ -0,0 +1,290 @@
{% extends 'schasite/base2.html' %}
{% load static %}
{% block pagetitle %}
<title>Stonehedge Community Homeowners Association</title>
<script type="text/css">
/* Links Page Specific Styles */
.links-table th {
background-color: var(--bs-success);
color: white;
font-weight: 500;
}
.links-table tr:hover {
background-color: rgba(var(--bs-success-rgb), 0.05);
}
/* Button styling */
.btn-outline-success {
border-width: 2px;
}
.btn-danger {
font-weight: 500;
}
/* Card header icons */
.card-header .bi {
font-size: 1.2rem;
vertical-align: text-top;
}
/* Responsive adjustments */
@media (max-width: 767.98px) {
.table-responsive {
font-size: 0.875rem;
}
.btn-sm {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}
}
</script>
{% endblock %}
{% block content %}
<main class="py-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="card shadow-sm">
<div class="card-header bg-success text-white">
<h3 class="mb-0"><i class="bi bi-exclamation-triangle me-2"></i>Useful Links</h3>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-success">
<tr>
<th>Resource</th>
<th>Link</th>
</tr>
</thead>
<tbody>
{% for link in links %}
<tr>
<td>{{ link.name }}</td>
<td><a href="{{ link.url }}" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Government Services -->
<!-- <div class="card shadow-sm mb-5">
<div class="card-header bg-success text-white">
<h3 class="mb-0"><i class="bi bi-building me-2"></i>Government Services</h3>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-success">
<tr>
<th>Service</th>
<th>Description</th>
<th>Link</th>
</tr>
</thead>
<tbody>
<tr>
<td>City of Anytown</td>
<td>Official city website with services, permits, and local information</td>
<td><a href="https://www.anytown.gov" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
<tr>
<td>County Services</td>
<td>Property records, tax payments, and county resources</td>
<td><a href="https://www.yourcounty.gov" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
<tr>
<td>Waste Management</td>
<td>Trash/recycling schedules, bulk pickup requests</td>
<td><a href="https://www.waste-management.com/your-area" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
<tr>
<td>Utility Company</td>
<td>Pay bills, report outages, manage services</td>
<td><a href="https://www.localutility.com" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
<tr>
<td>Police Non-Emergency</td>
<td>Local police department contact information</td>
<td><a href="https://www.anytownpd.gov" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
</tbody>
</table>
</div>
</div>
</div> -->
<!-- Schools & Education -->
<!-- <div class="card shadow-sm mb-5">
<div class="card-header bg-success text-white">
<h3 class="mb-0"><i class="bi bi-book me-2"></i>Schools & Education</h3>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-success">
<tr>
<th>School</th>
<th>Description</th>
<th>Link</th>
</tr>
</thead>
<tbody>
<tr>
<td>Greenwood Elementary</td>
<td>Public elementary school serving our community</td>
<td><a href="https://www.greenwood.edu/elementary" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
<tr>
<td>Maplewood Middle School</td>
<td>Public middle school serving our community</td>
<td><a href="https://www.greenwood.edu/middle" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
<tr>
<td>Central High School</td>
<td>Public high school serving our community</td>
<td><a href="https://www.greenwood.edu/high" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
<tr>
<td>School District</td>
<td>District calendar, policies, and information</td>
<td><a href="https://www.greenwood.edu" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
<tr>
<td>Community Library</td>
<td>Local library events and resources</td>
<td><a href="https://www.anytownlibrary.org" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
</tbody>
</table>
</div>
</div>
</div> -->
<!-- Local Businesses -->
<!-- <div class="card shadow-sm mb-5">
<div class="card-header bg-success text-white">
<h3 class="mb-0"><i class="bi bi-shop me-2"></i>Local Businesses</h3>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-success">
<tr>
<th>Business</th>
<th>Description</th>
<th>Link</th>
</tr>
</thead>
<tbody>
<tr>
<td>Greenwood Hardware</td>
<td>Local hardware store with home improvement supplies</td>
<td><a href="https://www.greenwoodhardware.com" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
<tr>
<td>Anytown Grocers</td>
<td>Local supermarket with delivery options</td>
<td><a href="https://www.anytowngrocers.com" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
<tr>
<td>Community Pharmacy</td>
<td>Local pharmacy with prescription services</td>
<td><a href="https://www.communitypharmacy.com" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
<tr>
<td>Preferred Landscapers</td>
<td>HOA-approved landscaping companies</td>
<td><a href="https://www.hoalandscapers.com/greenwood" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
<tr>
<td>Neighborhood Electrician</td>
<td>Recommended electrician familiar with our homes</td>
<td><a href="https://www.greenwoodelectric.com" target="_blank" class="btn btn-sm btn-outline-success">Visit Site</a></td>
</tr>
</tbody>
</table>
</div>
</div>
</div> -->
<!-- Emergency Resources -->
<!-- <div class="card shadow-sm">
<div class="card-header bg-success text-white">
<h3 class="mb-0"><i class="bi bi-exclamation-triangle me-2"></i>Emergency Resources</h3>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-success">
<tr>
<th>Resource</th>
<th>Description</th>
<th>Link</th>
</tr>
</thead>
<tbody>
<tr>
<td>Police/Fire Emergency</td>
<td>Call 911 for life-threatening emergencies</td>
<td><button class="btn btn-sm btn-danger">Call 911</button></td>
</tr>
<tr>
<td>Poison Control</td>
<td>24/7 poison emergency assistance</td>
<td><a href="tel:1-800-222-1222" class="btn btn-sm btn-outline-success">Call 800-222-1222</a></td>
</tr>
<tr>
<td>Animal Control</td>
<td>Report stray or dangerous animals</td>
<td><a href="tel:555-123-4567" class="btn btn-sm btn-outline-success">Call 555-123-4567</a></td>
</tr>
<tr>
<td>Power Outage Reporting</td>
<td>Report electrical outages in the area</td>
<td><a href="tel:555-987-6543" class="btn btn-sm btn-outline-success">Call 555-987-6543</a></td>
</tr>
<tr>
<td>Gas Leak Reporting</td>
<td>Report suspected natural gas leaks</td>
<td><a href="tel:555-555-5555" class="btn btn-sm btn-outline-success">Call 555-555-5555</a></td>
</tr>
</tbody>
</table>
</div>
</div>
</div> -->
</div>
</div>
</div>
</main>
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Links Page Script -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// This would be replaced with actual link tracking in production
console.log('Useful links page loaded');
// Example: Track outbound links
document.querySelectorAll('a[target="_blank"]').forEach(link => {
link.addEventListener('click', function() {
// In a real implementation, you might track this click
console.log('Navigating to: ' + this.href);
});
});
});
</script>
{% endblock %}

View File

@@ -11,7 +11,16 @@ urlpatterns = [
path("dues", views.dues, name="dues"),
path("membership_form", views.membership_form, name="membership_form"),
path("scha_board", views.scha_board, name="scha_board"),
# updated UI urls
path("index2", views.index2, name="index2"),
path("about_us2", views.about_us2, name="about_us2"),
path("calendar2", views.calendar2, name="calendar2"),
path("newsletters2", views.newsletters2, name="newsletters2"),
path("dues2", views.dues2, name="dues2"),
path("membership_form2", views.membership_form2, name="membership_form2"),
path("scha_board2", views.scha_board2, name="scha_board2"),
path("useful_links2", views.useful_links2, name="useful_links2"),
# stripe specific urls below
path('config/', views.stripe_config),
path('create-checkout-session/', views.create_checkout_session),
]
path("config/", views.stripe_config),
path("create-checkout-session/", views.create_checkout_session),
]

View File

@@ -1,66 +1,80 @@
from django.shortcuts import render, redirect
from .models import UsefulLinks, CalendarEvent, MembershipPerson, Payments
from .forms import ChildrenForm, AddressForm, PeopleForm, CommitteeForm, ServicesForm#, CaptchaForm
from .models import UsefulLinks, CalendarEvent, MembershipPerson, Payments, SCHAOfficer
from .forms import (
ChildrenForm,
AddressForm,
PeopleForm,
CommitteeForm,
ServicesForm,
) # , CaptchaForm
from django.db import transaction, IntegrityError
# Stripe required imports
from django.conf import settings # new
from django.conf import settings # new
from django.http.response import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt # new
import stripe
from django.views.decorators.csrf import csrf_exempt # new
import stripe
"""
Strip Stuff
Tutorial: https://testdriven.io/blog/django-stripe-tutorial/
"""
def dues(request):
return render(request, "schasite/dues.html", {})
@csrf_exempt
def stripe_config(request):
if request.method == 'GET':
stripe_config = {'publicKey': settings.STRIPE_PUBLISHABLE_KEY}
if request.method == "GET":
stripe_config = {"publicKey": settings.STRIPE_PUBLISHABLE_KEY}
return JsonResponse(stripe_config, safe=False)
@csrf_exempt
def create_checkout_session(request):
if request.method == 'GET':
if request.method == "GET":
domain_url = "http://localhost:8000/"
stripe.api_key = settings.STRIPE_SECRET_KEY
try:
checkout_session = stripe.checkout.Session.create(
success_url = domain_url+'success?session_id={CHECKOUT_SESSION_ID}',
cancel_url = domain_url+'cancelled/',
payment_method_types=['card'],
success_url=domain_url + "success?session_id={CHECKOUT_SESSION_ID}",
cancel_url=domain_url + "cancelled/",
payment_method_types=["card"],
mode="payment",
line_items = [{
# 'name':'SCHA Dues',
'quantity': 1,
# 'currency': 'usd',
'price': 'price_1OxZLfDV0RPXOyxG5ipjhUXk',
}]
line_items=[
{
# 'name':'SCHA Dues',
"quantity": 1,
# 'currency': 'usd',
"price": "price_1OxZLfDV0RPXOyxG5ipjhUXk",
}
],
)
return JsonResponse({'sessionId': checkout_session['id']})
return JsonResponse({"sessionId": checkout_session["id"]})
except Exception as e:
return JsonResponse({'error': str(e)})
return JsonResponse({"error": str(e)})
def stripe_success(request):
return render(request, "schasite/dues_success.html", {})
def stripe_cancelled(request):
return render(request, "schasite/dues_cancelled.html", {})
@csrf_exempt
def stripe_webhook(request):
stripe.api_key = settings.STRIPE_SECRET_KEY
endpoint_secret = settings.STRIPE_ENDPOINT_SECRET
payload = request.body
sig_header = request.META['HTTP_STRIPE_SIGNATURE']
sig_header = request.META["HTTP_STRIPE_SIGNATURE"]
event = None
try:
event = stripe.Webhook.construct_event(
payload, sig_header, endpoint_secret
)
event = stripe.Webhook.construct_event(payload, sig_header, endpoint_secret)
except ValueError as e:
# Invalid payload
return HttpResponse(status=400)
@@ -69,78 +83,136 @@ def stripe_webhook(request):
return HttpResponse(status=400)
# Handle the checkout.session.completed event
if event['type'] == 'checkout.session.completed':
if event["type"] == "checkout.session.completed":
email = None
try:
email = event['data']['object']['customer_details']['email']
email = event["data"]["object"]["customer_details"]["email"]
except:
pass
person = MembershipPerson.objects.filter(email=email).first() # just take the first
person = MembershipPerson.objects.filter(
email=email
).first() # just take the first
payment = Payments.objects.create(
email=email,
person = person
)
payment = Payments.objects.create(email=email, person=person)
# try to link to a member
payment.save()
# TODO: run some custom code here
return HttpResponse(status=200)
"""
Django Stuff
"""
def useful_links(request):
def useful_links(request):
useful_links = UsefulLinks.objects.all()
return render(request, "schasite/useful_links.html", {"links": useful_links})
def index(request):
return render(request, "schasite/index.html", {})
def about_us(request):
return render(request, "schasite/about_us.html", {})
def index2(request):
return render(request, "schasite/index2.html", {})
def calendar(request):
def about_us2(request):
return render(request, "schasite/about_us2.html", {})
def newsletters2(request):
return render(request, "schasite/newsletters2.html", {})
def calendar2(request):
all_events = CalendarEvent.objects.all()
future_events = [event if event.future_event() else None for event in all_events]
past_events = [event if event.past_event() else None for event in all_events]
# remove none for each list
sanitized_future_events = [i for i in future_events if i is not None]
sanitized_past_events = [i for i in past_events if i is not None]
return render(request, "schasite/calendar.html", {'future_events': sanitized_future_events,
'past_events': sanitized_past_events})
return render(
request,
"schasite/calendar2.html",
{
"future_events": sanitized_future_events,
"past_events": sanitized_past_events,
},
)
def newsletters(request):
return render(request, "schasite/newsletters.html", {})
def membership_form(request):
def scha_board2(request):
def get_officer(position_name):
try:
return SCHAOfficer.objects.get(position=position_name)
except:
return None
officers = [
get_officer("President"),
get_officer("1st Vice President"),
get_officer("2nd Vice President"),
get_officer("Treasurer"),
get_officer("Secretary"),
get_officer("Website"),
get_officer("Membership"),
get_officer("Directory"),
get_officer("Facebook"),
get_officer("Eblasts"),
]
return render(request, "schasite/scha_board2.html", {"officers": officers})
def dues2(request):
return render(request, "schasite/dues2.html", {})
def membership_form2(request):
def sanitize_phone_number(data):
if len(data) > 0:
data = data.replace('-','')
if not data.startswith('+1'):
data = '+1' + data
data = data.replace("-", "")
if not data.startswith("+1"):
data = "+1" + data
return data
if request.method == "POST":
# before we pass in the data we want to sanitize the phone numbers
post_data = request.POST.copy()
post_data.update({'person1-phone_number':sanitize_phone_number(post_data['person1-phone_number'])})
post_data.update({'person2-phone_number':sanitize_phone_number(post_data['person2-phone_number'])})
post_data.update(
{
"person1-phone_number": sanitize_phone_number(
post_data["person1-phone_number"]
)
}
)
post_data.update(
{
"person2-phone_number": sanitize_phone_number(
post_data["person2-phone_number"]
)
}
)
membershipForm = ChildrenForm(post_data)
addressForm = AddressForm(post_data)
peopleForm1 = PeopleForm(post_data, prefix='person1')
peopleForm2 = PeopleForm(post_data, prefix='person2')
peopleForm1 = PeopleForm(post_data, prefix="person1")
peopleForm2 = PeopleForm(post_data, prefix="person2")
servicesForm = ServicesForm(post_data)
committeeForm= CommitteeForm(post_data)
committeeForm = CommitteeForm(post_data)
# captchaForm = CaptchaForm(post_data)
if membershipForm.is_valid() and addressForm.is_valid() and committeeForm.is_valid() and (peopleForm1.is_valid() or peopleForm2.is_valid()) and servicesForm.is_valid(): # and captchaForm.is_valid():
if (
membershipForm.is_valid()
and addressForm.is_valid()
and committeeForm.is_valid()
and (peopleForm1.is_valid() or peopleForm2.is_valid())
and servicesForm.is_valid()
): # and captchaForm.is_valid():
with transaction.atomic():
membershipForm = ChildrenForm({**post_data})
membership = membershipForm.save(commit=False)
membership.save()
if peopleForm1.is_valid():
people1_obj = peopleForm1.save(commit=False)
people1_obj.membership = membership
@@ -163,37 +235,183 @@ def membership_form(request):
address_obj.membership = membership
address_obj.save()
return redirect('index')
return redirect("index")
else:
return render(request,
"schasite/membership_form.html",
{
'membershipForm': ChildrenForm,
'peopleForm1': peopleForm1,
'peopleForm2': peopleForm2,
'addressForm': addressForm,
'committeeForm' : committeeForm,
'servicesForm': servicesForm,
# 'captchaForm': captchaForm,
} )
return render(
request,
"schasite/membership_form2.html",
{
"membershipForm": ChildrenForm,
"peopleForm1": peopleForm1,
"peopleForm2": peopleForm2,
"addressForm": addressForm,
"committeeForm": committeeForm,
"servicesForm": servicesForm,
# 'captchaForm': captchaForm,
},
)
else:
return render(request,
"schasite/membership_form.html",
{'membershipForm': ChildrenForm(),
'peopleForm1': PeopleForm(prefix='person1'),
'peopleForm2': PeopleForm(prefix='person2'),
'committeeForm' : CommitteeForm(),
'servicesForm': ServicesForm(),
# 'captchaForm': CaptchaForm(),
"addressForm": AddressForm(
initial={
"city": "Wheaton",
"state": "IL",
"zip_code": 60189,
}
)})
return render(
request,
"schasite/membership_form2.html",
{
"membershipForm": ChildrenForm(),
"peopleForm1": PeopleForm(prefix="person1"),
"peopleForm2": PeopleForm(prefix="person2"),
"committeeForm": CommitteeForm(),
"servicesForm": ServicesForm(),
# 'captchaForm': CaptchaForm(),
"addressForm": AddressForm(
initial={
"city": "Wheaton",
"state": "IL",
"zip_code": 60189,
}
),
},
)
def useful_links2(request):
useful_links = UsefulLinks.objects.all()
return render(request, "schasite/useful_links2.html", {"links": useful_links})
def index(request):
return render(request, "schasite/index.html", {})
def about_us(request):
return render(request, "schasite/about_us.html", {})
def calendar(request):
all_events = CalendarEvent.objects.all()
future_events = [event if event.future_event() else None for event in all_events]
past_events = [event if event.past_event() else None for event in all_events]
# remove none for each list
sanitized_future_events = [i for i in future_events if i is not None]
sanitized_past_events = [i for i in past_events if i is not None]
return render(
request,
"schasite/calendar.html",
{
"future_events": sanitized_future_events,
"past_events": sanitized_past_events,
},
)
def newsletters(request):
return render(request, "schasite/newsletters.html", {})
def membership_form(request):
def sanitize_phone_number(data):
if len(data) > 0:
data = data.replace("-", "")
if not data.startswith("+1"):
data = "+1" + data
return data
if request.method == "POST":
# before we pass in the data we want to sanitize the phone numbers
post_data = request.POST.copy()
post_data.update(
{
"person1-phone_number": sanitize_phone_number(
post_data["person1-phone_number"]
)
}
)
post_data.update(
{
"person2-phone_number": sanitize_phone_number(
post_data["person2-phone_number"]
)
}
)
membershipForm = ChildrenForm(post_data)
addressForm = AddressForm(post_data)
peopleForm1 = PeopleForm(post_data, prefix="person1")
peopleForm2 = PeopleForm(post_data, prefix="person2")
servicesForm = ServicesForm(post_data)
committeeForm = CommitteeForm(post_data)
# captchaForm = CaptchaForm(post_data)
if (
membershipForm.is_valid()
and addressForm.is_valid()
and committeeForm.is_valid()
and (peopleForm1.is_valid() or peopleForm2.is_valid())
and servicesForm.is_valid()
): # and captchaForm.is_valid():
with transaction.atomic():
membershipForm = ChildrenForm({**post_data})
membership = membershipForm.save(commit=False)
membership.save()
if peopleForm1.is_valid():
people1_obj = peopleForm1.save(commit=False)
people1_obj.membership = membership
people1_obj.save()
if peopleForm2.is_valid():
people2_obj = peopleForm2.save(commit=False)
people2_obj.membership = membership
people2_obj.save()
committee_obj = committeeForm.save(commit=False)
committee_obj.membership = membership
committee_obj.save()
services_obj = servicesForm.save(commit=False)
services_obj.membership = membership
services_obj.save()
address_obj = addressForm.save(commit=False)
address_obj.membership = membership
address_obj.save()
return redirect("index")
else:
return render(
request,
"schasite/membership_form.html",
{
"membershipForm": ChildrenForm,
"peopleForm1": peopleForm1,
"peopleForm2": peopleForm2,
"addressForm": addressForm,
"committeeForm": committeeForm,
"servicesForm": servicesForm,
# 'captchaForm': captchaForm,
},
)
else:
return render(
request,
"schasite/membership_form.html",
{
"membershipForm": ChildrenForm(),
"peopleForm1": PeopleForm(prefix="person1"),
"peopleForm2": PeopleForm(prefix="person2"),
"committeeForm": CommitteeForm(),
"servicesForm": ServicesForm(),
# 'captchaForm': CaptchaForm(),
"addressForm": AddressForm(
initial={
"city": "Wheaton",
"state": "IL",
"zip_code": 60189,
}
),
},
)
def scha_board(request):
return render(request, "schasite/scha_board.html", {})
return render(request, "schasite/scha_board.html", {})