diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..80fb39c --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'scha.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..602b2e9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +stripe +django +django-adminplus +django-recaptcha \ No newline at end of file diff --git a/scha/__init__.py b/scha/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scha/asgi.py b/scha/asgi.py new file mode 100644 index 0000000..ffcdfcd --- /dev/null +++ b/scha/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for scha project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'scha.settings') + +application = get_asgi_application() diff --git a/scha/settings.py b/scha/settings.py new file mode 100644 index 0000000..e349dc8 --- /dev/null +++ b/scha/settings.py @@ -0,0 +1,133 @@ +""" +Django settings for scha project. + +Generated by 'django-admin startproject' using Django 4.2.10. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.2/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# 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*' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +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', +] + +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', +] + +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', + ], + }, + }, +] + +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', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/4.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.2/howto/static-files/ + +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' + +# Recaptcha Stuff +RECAPTCHA_PUBLIC_KEY = '6LesfrkpAAAAAGJ5vYp4KuuGcyfk70HkihCwp3d3' +RECAPTCHA_PRIVATE_KEY = '6LesfrkpAAAAABCcDGli5cZiq1hfS0lfRiXg0mlI' \ No newline at end of file diff --git a/scha/urls.py b/scha/urls.py new file mode 100644 index 0000000..a10a8f2 --- /dev/null +++ b/scha/urls.py @@ -0,0 +1,30 @@ +""" +URL configuration for scha project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +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 +from django.conf import settings + +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), +] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/scha/wsgi.py b/scha/wsgi.py new file mode 100644 index 0000000..58831fc --- /dev/null +++ b/scha/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for scha project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'scha.settings') + +application = get_wsgi_application() diff --git a/schasite/__init__.py b/schasite/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/schasite/admin.py b/schasite/admin.py new file mode 100644 index 0000000..c04bfdd --- /dev/null +++ b/schasite/admin.py @@ -0,0 +1,192 @@ +from django.contrib import admin +from .models import UsefulLinks, Membership,CalendarEvent, MembershipServices, AddressModel1, MembershipPerson, MembershipCommittee, CalendarEventAddressModel, Payments +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',) + +class MembershipPersonInline(admin.TabularInline): + model = MembershipPerson + extra = 1 + readonly_fields = ('id',) + +class MembershipCommiteeInline(admin.TabularInline): + model = MembershipCommittee + extra = 1 + readonly_fields = ('id',) + +class MembershipServicesInline(admin.TabularInline): + model = MembershipServices + extra = 1 + readonly_fields = ('id',) + +def download_csv_by_members(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', + 'first_name', + 'last_name', + 'email', + 'phone_number' + ]) + for q in queryset: + 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 + ]) + 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) + 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"]) + + 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 "", + ]) + 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) + return response + +class MembershipAdmin(admin.ModelAdmin): + 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',) + +class CalendarEventAdmin(admin.ModelAdmin): + inlines = [CalendarEventAddressInline] + 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 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' + ]) + for q in queryset: + 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, + ]) + 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) + return response + +class PaymentsAdmin(admin.ModelAdmin): + list_display = ["date", "status", "email"] + search_fields = ['email'] + actions=[download_payments] + form = PaymentImport + +admin.site.register(UsefulLinks, UsefulLinksAdmin) +admin.site.register(Membership, MembershipAdmin) +admin.site.register(CalendarEvent, CalendarEventAdmin) +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) \ No newline at end of file diff --git a/schasite/apps.py b/schasite/apps.py new file mode 100644 index 0000000..e93d8f0 --- /dev/null +++ b/schasite/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SchasiteConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'schasite' diff --git a/schasite/forms.py b/schasite/forms.py new file mode 100644 index 0000000..fbca79a --- /dev/null +++ b/schasite/forms.py @@ -0,0 +1,101 @@ +from django import forms +from django.forms import ModelForm +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 + +# class CaptchaForm(forms.Form): +# captcha = ReCaptchaField( +# public_key=settings.RECAPTCHA_PUBLIC_KEY, +# private_key=settings.RECAPTCHA_PRIVATE_KEY, +# widget=ReCaptchaV3( +# attrs={ +# 'required_score':0.85, +# } +# ), + +# ) + +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"] + +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') + else: + raise ValidationError('No phone number provided') + + class Meta: + model = MembershipPerson + 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) + 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) + 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) + 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) + class Meta: + model = MembershipCommittee + fields = [ + "block_captain", + "coordinator", + "egg_hunt", + "spring_garage_sale", + "golf_outing", + "ice_cream_social", + "fall_garage_sale", + "halloween_party", + "santa_visit", + "website", + "civic_affair", + "phone_directory", + "no_preference", + ] + +class ServicesForm(ModelForm): + babysitting = forms.BooleanField(label="Babysitting", required=False) + lawn_mowing = forms.BooleanField(label="Lawn Mowing", required=False) + snow_shoveling = forms.BooleanField(label="Snow Shoveling", required=False) + leaf_raking = forms.BooleanField(label="Leaf Raking", required=False) + 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 = [ + "babysitting", + "lawn_mowing", + "snow_shoveling", + "leaf_raking", + "petsitting", + "house_sitting", + "other", + "other_desc", + ] + +class PaymentImport(ModelForm): + pass \ No newline at end of file diff --git a/schasite/migrations/0001_initial.py b/schasite/migrations/0001_initial.py new file mode 100644 index 0000000..3b7bfc1 --- /dev/null +++ b/schasite/migrations/0001_initial.py @@ -0,0 +1,99 @@ +# Generated by Django 4.2.10 on 2024-03-20 13:28 + +from django.db import migrations, models +import django.db.models.deletion +import phonenumber_field.modelfields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + 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)), + ], + ), + migrations.CreateModel( + name='Membership', + fields=[ + ('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', + 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)), + ], + ), + migrations.CreateModel( + 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')), + ], + ), + migrations.CreateModel( + 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')), + ], + ), + migrations.CreateModel( + 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')), + ], + ), + migrations.CreateModel( + 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')), + ], + ), + ] diff --git a/schasite/migrations/0002_alter_membership_children.py b/schasite/migrations/0002_alter_membership_children.py new file mode 100644 index 0000000..97547b0 --- /dev/null +++ b/schasite/migrations/0002_alter_membership_children.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.10 on 2024-03-20 15:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('schasite', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='membership', + name='children', + field=models.CharField(blank=True, default='', max_length=256, null=True), + ), + ] diff --git a/schasite/migrations/0003_alter_membershipcommittee_block_captain_and_more.py b/schasite/migrations/0003_alter_membershipcommittee_block_captain_and_more.py new file mode 100644 index 0000000..9b2ff5f --- /dev/null +++ b/schasite/migrations/0003_alter_membershipcommittee_block_captain_and_more.py @@ -0,0 +1,78 @@ +# Generated by Django 4.2.10 on 2024-03-20 15:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('schasite', '0002_alter_membership_children'), + ] + + operations = [ + migrations.AlterField( + model_name='membershipcommittee', + name='block_captain', + field=models.BooleanField(blank=True, default=False, null=True), + ), + migrations.AlterField( + model_name='membershipcommittee', + name='civic_affair', + field=models.BooleanField(blank=True, default=False, null=True), + ), + migrations.AlterField( + model_name='membershipcommittee', + name='coordinator', + field=models.BooleanField(blank=True, default=False, null=True), + ), + migrations.AlterField( + model_name='membershipcommittee', + name='egg_hunt', + field=models.BooleanField(blank=True, default=False, null=True), + ), + migrations.AlterField( + model_name='membershipcommittee', + name='fall_garage_sale', + field=models.BooleanField(blank=True, default=False, null=True), + ), + migrations.AlterField( + model_name='membershipcommittee', + name='golf_outing', + field=models.BooleanField(blank=True, default=False, null=True), + ), + migrations.AlterField( + model_name='membershipcommittee', + name='halloween_party', + field=models.BooleanField(blank=True, default=False, null=True), + ), + migrations.AlterField( + model_name='membershipcommittee', + name='ice_cream_social', + field=models.BooleanField(blank=True, default=False, null=True), + ), + migrations.AlterField( + model_name='membershipcommittee', + name='no_preference', + field=models.BooleanField(blank=True, default=False, null=True), + ), + migrations.AlterField( + model_name='membershipcommittee', + name='phone_directory', + field=models.BooleanField(blank=True, default=False, null=True), + ), + migrations.AlterField( + model_name='membershipcommittee', + name='santa_visit', + field=models.BooleanField(blank=True, default=False, null=True), + ), + migrations.AlterField( + model_name='membershipcommittee', + name='spring_garage_sale', + field=models.BooleanField(blank=True, default=False, null=True), + ), + migrations.AlterField( + model_name='membershipcommittee', + name='website', + field=models.BooleanField(blank=True, default=False, null=True), + ), + ] diff --git a/schasite/migrations/0004_alter_membershipperson_phone_number.py b/schasite/migrations/0004_alter_membershipperson_phone_number.py new file mode 100644 index 0000000..eee92f4 --- /dev/null +++ b/schasite/migrations/0004_alter_membershipperson_phone_number.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.10 on 2024-03-22 13:40 + +from django.db import migrations +import phonenumber_field.modelfields + + +class Migration(migrations.Migration): + + dependencies = [ + ('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), + ), + ] diff --git a/schasite/migrations/0005_membershipservices.py b/schasite/migrations/0005_membershipservices.py new file mode 100644 index 0000000..e9c4155 --- /dev/null +++ b/schasite/migrations/0005_membershipservices.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.10 on 2024-03-22 14:07 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('schasite', '0004_alter_membershipperson_phone_number'), + ] + + operations = [ + migrations.CreateModel( + 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')), + ], + ), + ] diff --git a/schasite/migrations/0006_calendareventaddressmodel_and_more.py b/schasite/migrations/0006_calendareventaddressmodel_and_more.py new file mode 100644 index 0000000..fd91a7f --- /dev/null +++ b/schasite/migrations/0006_calendareventaddressmodel_and_more.py @@ -0,0 +1,37 @@ +# Generated by Django 4.2.10 on 2024-03-23 18:03 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('schasite', '0005_membershipservices'), + ] + + operations = [ + migrations.CreateModel( + 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)), + ], + ), + migrations.RemoveField( + model_name='calendarevent', + name='location_address', + ), + migrations.DeleteModel( + name='AddressModel', + ), + migrations.AddField( + model_name='calendareventaddressmodel', + name='calendar_event', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='schasite.calendarevent'), + ), + ] diff --git a/schasite/migrations/0007_payments.py b/schasite/migrations/0007_payments.py new file mode 100644 index 0000000..1605a23 --- /dev/null +++ b/schasite/migrations/0007_payments.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.10 on 2024-03-27 17:40 + +import datetime +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('schasite', '0006_calendareventaddressmodel_and_more'), + ] + + operations = [ + migrations.CreateModel( + 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')), + ], + ), + ] diff --git a/schasite/migrations/0008_alter_payments_date.py b/schasite/migrations/0008_alter_payments_date.py new file mode 100644 index 0000000..0823b19 --- /dev/null +++ b/schasite/migrations/0008_alter_payments_date.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.10 on 2024-03-27 17:41 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('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)), + ), + ] diff --git a/schasite/migrations/__init__.py b/schasite/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/schasite/models.py b/schasite/models.py new file mode 100644 index 0000000..f57e5e7 --- /dev/null +++ b/schasite/models.py @@ -0,0 +1,128 @@ +from django.db import models +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) + url = models.CharField(max_length=256) + + +class Membership(models.Model): + children = models.CharField(max_length=256, default="", blank=True, null=True) + + def get_address_str(self): + if self.addressmodel1 is None: + return "No Address" + elif self.addressmodel1.address_1 is None: + return "No Address" + else: + return self.addressmodel1.address_1 + + def get_person_1(self): + emails = [item.email for item in MembershipPerson.objects.filter(membership_id=self.id)] + # remove all the Nones + filtered_emails = [i for i in emails if i is not None] + if len(filtered_emails) == 0: + return "No Person" + else: + return " | ".join(filtered_emails) + + def __str__(self): + return self.get_address_str() + " | " + self.get_person_1() + +class AddressModel1(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) + + + +class CalendarEvent(models.Model): + 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(max_length=256, blank=True, null=True) + coordinator_email = models.EmailField(max_length=256, blank=True, null=True) + event_link_name = models.CharField(max_length=64, blank=True, null=True) + event_url = models.URLField(max_length=256, blank=True, null=True) + + 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) + + 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) + first_name = models.CharField(max_length=256, blank=True, null=True) + last_name = models.CharField(max_length=256, blank=True, null=True) + phone_number = PhoneNumberField(blank=False, null=True) + email = models.EmailField(max_length=254, blank=True, null=True) + + def __str__(self): + return self.email if self.email else "No email" + + + + +class MembershipCommittee(models.Model): + membership = models.OneToOneField(Membership, on_delete=models.CASCADE) + block_captain = models.BooleanField(default=False, blank=True, null=True) + coordinator = models.BooleanField(default=False, blank=True, null=True) + egg_hunt = models.BooleanField(default=False, blank=True, null=True) + spring_garage_sale = models.BooleanField(default=False, blank=True, null=True) + golf_outing = models.BooleanField(default=False, blank=True, null=True) + ice_cream_social = models.BooleanField(default=False, blank=True, null=True) + fall_garage_sale = models.BooleanField(default=False, blank=True, null=True) + halloween_party = models.BooleanField(default=False, blank=True, null=True) + santa_visit = models.BooleanField(default=False, blank=True, null=True) + website = models.BooleanField(default=False, blank=True, null=True) + civic_affair = models.BooleanField(default=False, blank=True, null=True) + phone_directory = models.BooleanField(default=False, blank=True, null=True) + no_preference = models.BooleanField(default=False, blank=True, null=True) + +class MembershipServices(models.Model): + membership = models.OneToOneField(Membership, on_delete=models.CASCADE) + babysitting = models.BooleanField(default=False, blank=True, null=True) + lawn_mowing = models.BooleanField(default=False, blank=True, null=True) + snow_shoveling = models.BooleanField(default=False, blank=True, null=True) + leaf_raking = models.BooleanField(default=False, blank=True, null=True) + petsitting = models.BooleanField(default=False, blank=True, null=True) + house_sitting = models.BooleanField(default=False, blank=True, null=True) + other = models.BooleanField(default=False, blank=True, null=True) + other_desc = models.CharField(default="", blank=True, null=True, max_length=256) + +class Payments(models.Model): + date = models.DateField(default=timezone.now()) + status = models.CharField(default='Completed', max_length=256) + email = models.EmailField(blank=True, null=True) + person = models.ForeignKey(MembershipPerson, on_delete=models.CASCADE, blank=True,null=True) \ No newline at end of file diff --git a/schasite/static/css/style.css b/schasite/static/css/style.css new file mode 100644 index 0000000..4d8e6e1 --- /dev/null +++ b/schasite/static/css/style.css @@ -0,0 +1,156 @@ +/* CSS Document */ +{% load static %} +body{ + margin:0px; + background-repeat: repeat; + font-family:Verdana, Arial, Helvetica, sans-serif; font-size:12px; + } +a{color:#63880a;} +h1{ + font-family:Arial, Helvetica, sans-serif; font-size:25px; + color:#63880a; line-height:normal; + margin:0px; padding:30px 0px 0px 0px; + } +h1 span{color:#979797;} +.clear{margin:0px; padding:0px; clear:both;} +.top-head{ + height:118px; width:1000px; margin:auto; + background:url(../images/top-head.gif) repeat-x; + } +.logo{ + height:103px; width:325px; + float:left; padding:15px 0px 0px 55px; + } +.right-top{ + height:83px; width:620px; float:left; + padding:35px 0px 0px 0px; + } +.tag-line{ + height:auto; width:175px; float:left; padding:0px 10px 0px 20px; + color:#8b8b8b; font-size:11px; font-family:Verdana, Arial, Helvetica, sans-serif; + line-height:22px; border-left:#cad79f solid 1px; text-transform:uppercase; font-weight:bold; + } +.punch-line{ + height:auto; width:370px; float:left; padding:0px 20px 0px 20px; + color:#4a4a4a; font-size:11px; font-family:Verdana, Arial, Helvetica, sans-serif; + line-height:22px; border-left:#cad79f solid 1px; font-weight:bold; font-style:italic; + } +.punch-line a{color:#9db54e;} +#header{ + height:279px; width:445px; margin:auto; + background:url(../images/header.gif) no-repeat; + padding:40px 500px 0px 55px; + font-family:Verdana, Arial, Helvetica, sans-serif; color:#FFFFFF; + font-size:23px; line-height:26px; font-weight:normal; + } +#header ul{margin:0px; padding:20px 0px 0px 20px;} +#header ul li{ + font-family:Verdana, Arial, Helvetica, sans-serif; font-size:13px; + color:#FFFFFF; font-weight:bold; + } +#header ul li a{color:#FFFFFF;} +.nav-bar{ + height:56px; width:1000px; margin:auto; + background:url(../images/nav-bar.gif) repeat-x; + } +.nav-bar ul{ + margin:0px; + padding:0px 0px 0px 30px; + list-style-type:none; + } +.nav-bar ul li{ + height:36px; width:auto; float:left; + background:url(../images/list-left.gif) no-repeat; + display:block; + padding:20px 12px 0px 12px; + } +.nav-bar ul li a{ + font-family:Arial, Helvetica, sans-serif; font-size:14px; text-decoration:none; + color:#FFFFFF; + background:url(../images/list-arrow.gif) no-repeat; + font-weight:normal; + padding-left:22px; font-weight:bold; + background-position: 6px; + } +.top-body{ + height:auto; width:940px; margin:auto; + background: url(../images/top-body.gif) repeat-x #cfcfcf; + padding:0px 0px 25px 60px; + } +.top-body-con{ + height:auto; width:265px; float:left; + padding:0px 35px 0px 0px; + } +.top-body-con p{ + margin:0px; padding:30px 0px 0px 0px; + font-family:Verdana, Arial, Helvetica, sans-serif; color:#2e2e2e; + font-size:11px; font-weight:bold; line-height:26px; + } +.top-body-con a{color:#63880a;} +.top-body-con span{color:#63880a; font-size:15px;} +.img-box{ + height:auto; width:auto; float:left; + padding:30px 25px 0px 0px; + } +.img-box-1{ + height:auto; width:auto; float:right; + padding:20px 0px 0px 25px; + } +.mid-body{ + height:auto; width:890px; margin:auto; + background:url(../images/mid-body.gif) repeat-x #FFFFFF; + padding:0px 50px 30px 60px; + } +.mid-body p{ + margin:0px; padding:10px 0px 0px 0px; + font-family:Verdana, Arial, Helvetica, sans-serif; color:#343434; + font-size:14px; line-height:26px; + } +.mid-body a{color:#63880a;} +.mid-body p span{color:#343434; font-size:17px;} +.bt-cont{ + height:auto; width:930px; margin:auto; + background:url(../images/rest-con.gif) repeat-x #e8edda; + padding:0px 0px 20px 70px; + } +.cont-rl{ + height:auto; width:300px; float:left; + } +.bt-cont p{ + margin:0px; padding:30px 0px 0px 0px; + font-family:Verdana, Arial, Helvetica, sans-serif; color:#2e2e2e; + font-size:14px; line-height:30px; + } +.bt-cont a{color:#63880a;} +.bt-cont span{color:#63880a; font-size:20px; font-weight:normal;} +#footer{ + height:25px; width:880px; margin:auto; + text-align:left; font-family:Verdana, Arial, Helvetica, sans-serif; + color:#FFFFFF; font-size:11px; background:url(../images/footer.gif) repeat-x; + padding:15px 60px 0px 60px; + } +#footer strong{ + float:right; color:#FFFFFF; + } +#footer a{ color:#FFFFFF; } + +/* inner pages css start */ +h1.inner{font:33px Myriad Pro, Arial; color:#fff; font-weight:100; margin:0px; padding:25px 0px 10px 0px; background:none} +.aboutus-img{float:right; border:4px solid #b0a48b; margin:0px 0px 10px 20px;} +h5{font:15px Myriad Pro, Arial, Helvetica, sans-serif; color:#63880a; font-weight:bold; padding:0px 0px 5px 0px; border-bottom:1px dotted #666666; margin:0px 0px 10px 0px;} +h6{font:17px Myriad Pro, Arial, Helvetica, sans-serif; color:#63880a; font-weight:bold; padding:0px 0px 5px 0px; margin:0px 0px 10px 0px;} +.aboutcolumnzone{padding:20px 0px 16px 0px;} +.aboutcolumn1{width:48%; float:left; margin:0px 0px 10px 0px;} +.aboutcolumn2{width:48%; float:right; margin:0px 0px 10px 0px;} +.abouticon{float:left; margin:0px 20px 0px 0px;} +.insidereadmore{padding:10px 0px 10px 0px;} +input.button{color:#ffffff; background:#414141; font:bold 11px Arial, Helvetica, sans-serif; text-decoration:none; padding:10px 10px; margin:0px 5px 5px 0; border:1px solid #000000;} +input.button:hover{cursor:pointer; color:#cccccc;} +.project-img{float:right; margin-left:20px; border: 6px solid #a1b96b;} +.whiteheading{font:30px Myriad Pro, Arial; color:#ffffff; font-weight:100; padding:0px; margin:25px 0px 20px 0px;} +.ourprojectrow{margin-bottom:20px; border-bottom:1px dotted #666666; padding-bottom:10px; width: 95%;} +.servicecolumnzone{padding:20px 0px 16px 0px;} +.servicecolumn1{width:48%; float:left; margin:0px 0px 10px 0px;} +.servicecolumn2{width:48%; float:right; margin:0px 0px 10px 0px;} +.blog-posted-row{padding:3px;} +/* inner pages css ends */ diff --git a/schasite/static/images/25.gif b/schasite/static/images/25.gif new file mode 100644 index 0000000..5fd3d36 Binary files /dev/null and b/schasite/static/images/25.gif differ diff --git a/schasite/static/images/bricks.jpg b/schasite/static/images/bricks.jpg new file mode 100644 index 0000000..44a2e4a Binary files /dev/null and b/schasite/static/images/bricks.jpg differ diff --git a/schasite/static/images/dues.jpeg b/schasite/static/images/dues.jpeg new file mode 100644 index 0000000..ab8d06f Binary files /dev/null and b/schasite/static/images/dues.jpeg differ diff --git a/schasite/static/images/easter.gif b/schasite/static/images/easter.gif new file mode 100644 index 0000000..ccffa24 Binary files /dev/null and b/schasite/static/images/easter.gif differ diff --git a/schasite/static/images/facebook.gif b/schasite/static/images/facebook.gif new file mode 100644 index 0000000..2eadfa0 Binary files /dev/null and b/schasite/static/images/facebook.gif differ diff --git a/schasite/static/images/footer.gif b/schasite/static/images/footer.gif new file mode 100644 index 0000000..27d8ade Binary files /dev/null and b/schasite/static/images/footer.gif differ diff --git a/schasite/static/images/garage_sale.gif b/schasite/static/images/garage_sale.gif new file mode 100644 index 0000000..527c5cd Binary files /dev/null and b/schasite/static/images/garage_sale.gif differ diff --git a/schasite/static/images/header.gif b/schasite/static/images/header.gif new file mode 100644 index 0000000..d3cffcf Binary files /dev/null and b/schasite/static/images/header.gif differ diff --git a/schasite/static/images/jointoday.gif b/schasite/static/images/jointoday.gif new file mode 100644 index 0000000..9cec6d4 Binary files /dev/null and b/schasite/static/images/jointoday.gif differ diff --git a/schasite/static/images/list-arrow.gif b/schasite/static/images/list-arrow.gif new file mode 100644 index 0000000..91e22e0 Binary files /dev/null and b/schasite/static/images/list-arrow.gif differ diff --git a/schasite/static/images/list-left.gif b/schasite/static/images/list-left.gif new file mode 100644 index 0000000..04085c2 Binary files /dev/null and b/schasite/static/images/list-left.gif differ diff --git a/schasite/static/images/memberverification (1).gif b/schasite/static/images/memberverification (1).gif new file mode 100644 index 0000000..8e5e558 Binary files /dev/null and b/schasite/static/images/memberverification (1).gif differ diff --git a/schasite/static/images/memberverification.gif b/schasite/static/images/memberverification.gif new file mode 100644 index 0000000..8e5e558 Binary files /dev/null and b/schasite/static/images/memberverification.gif differ diff --git a/schasite/static/images/mid-body.gif b/schasite/static/images/mid-body.gif new file mode 100644 index 0000000..14e6996 Binary files /dev/null and b/schasite/static/images/mid-body.gif differ diff --git a/schasite/static/images/nav-bar.gif b/schasite/static/images/nav-bar.gif new file mode 100644 index 0000000..bed3b57 Binary files /dev/null and b/schasite/static/images/nav-bar.gif differ diff --git a/schasite/static/images/news (1).gif b/schasite/static/images/news (1).gif new file mode 100644 index 0000000..8867111 Binary files /dev/null and b/schasite/static/images/news (1).gif differ diff --git a/schasite/static/images/news.gif b/schasite/static/images/news.gif new file mode 100644 index 0000000..8867111 Binary files /dev/null and b/schasite/static/images/news.gif differ diff --git a/schasite/static/images/rest-con.gif b/schasite/static/images/rest-con.gif new file mode 100644 index 0000000..c6a0cd0 Binary files /dev/null and b/schasite/static/images/rest-con.gif differ diff --git a/schasite/static/main.js b/schasite/static/main.js new file mode 100644 index 0000000..97b5984 --- /dev/null +++ b/schasite/static/main.js @@ -0,0 +1,24 @@ +// static/main.js + +console.log("Sanity Check"); + +fetch("/schasite/config/") +.then((result) => {return result.json(); }) +.then((data) => { + const stripe = Stripe(data.publicKey); + + // Event Handler + document.querySelector("#submitBtn").addEventListener("click", () => { + // Get Checkout Session ID + fetch("/schasite/create-checkout-session/") + .then((result) => { return result.json(); }) + .then((data) => { + console.log(data); + // Redirect to Stripe Checkout + return stripe.redirectToCheckout({sessionId: data.sessionId}) + }) + .then((res) => { + console.log(res); + }); + }); +}); \ No newline at end of file diff --git a/schasite/templates/schasite/about_us.html b/schasite/templates/schasite/about_us.html new file mode 100644 index 0000000..2b35771 --- /dev/null +++ b/schasite/templates/schasite/about_us.html @@ -0,0 +1,41 @@ +{% extends 'schasite/base.html' %} +{% load static %} + +{% block pagetitle %} +
Education
+The Stonehedge Subdivision is proud to have excellent public and private schools. The public schools are part of Community Unit School District 200. Stonehedge subdivision attends:
+Whittier Elementary School, 218 W. Park Ave., Wheaton, IL 60189, (630) 682-2185
+ Whittier's Web site
Edison Middle School, 1125 S. Wheaton Ave., Wheaton, IL 60189, (630) 682-2050
+ Edison's Web site
Wheaton Warrenville South High School, 1920 S. Wiesbrook Rd., Wheaton, IL 60189, (630) 784-7200
+ WWSHS's Web site
There are many private schools in the area as well including Wheaton Christian Grammar School, Wheaton Montessori School, St. John Lutheran School, St. Michael Elementary School, St. Francis High School, Benet Academy and many more. Colleges include Devry, Benedictine, College of DuPage, Wheaton College, IIT, National Louis University, & North Central College.
+History
+In 1977, Levitt Homes began building the Stonehedge Subdivision. 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. Both Orchard Cove & Marywood were built by Keim.
+These 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.
+Shopping
+Within walking distance and just east of the subdivisions, there are plenty of shopping centers including Danada East, Danada West, Towne Square & Rice Lake. You'll find supermarkets, post office, banks, restaurants, gas stations, craft stores, department stores, furniture stores, book stores, movie rental stores, dry cleaning facilities, ice cream stores, coffee shops and many other specialty stores.
+Parks & Recreation
+The Stonehedge area is ideally suited for recreation. Seven Gables Park, at Brighton and Chatham, is maintained by the Wheaton Park District. It boasts a children's playground, a lake, soccer and baseball fields, picnic shelters, a 1.2 mile paved trail and a fitness course. In addition, there are several smaller parks also located directly within the subdivisions including Ridge Park & Brighton Park. Nearby is the state-of-the-art Rice Water Park and the Wheaton Park District Community Center, which includes a health and fitness center and indoor walking/jogging track and teen center. Arrowhead Golf Course, Herrick Lake, Blackwell Recreation Area, and the Illinois Prairie Path are just west on Butterfield Road.
+Date: {{ event.start_date }}
+ {% else %} +Date: {{ event.start_date }} - {{ event.end_date }}
+ {% endif %} +Location: {{ event.location_name }} - {{ event.calendareventaddressmodel }}
+ {% if event.coordinator_email %} +Coordinator: {{ event.coordinator_email }}
+ {% endif %} + {% if event.event_url and event.event_link_name %} + + {% endif %} + {% endfor %} + {% else %} +Stay tuned for more events!
+ {% endif %} + +Date: {{ event.start_date }}
+ {% else %} +Date: {{ event.start_date }} - {{ event.end_date }}
+ {% endif %} +Location: {{ event.location_name }} - {{ event.calendareventaddressmodel }}
+ {% if event.coordinator_email %} +Coordinator: {{ event.coordinator_email }}
+ {% endif %} + {% if event.event_url and event.event_link_name %} + + {% endif %} + {% endfor %} + {% else %} +Stay tuned for more events!
+ {% endif %} + + +Event Feedack Coming Soon!
+
Ready to do some spring cleaning? Check back for the SCHA Spring Garage Sale dates and registration form.
+
+
+
Bring the kids for our annual Easter Egg Hunt. More details coming soon.
+
+
+
Become a Stonehedge member and your membership dues help to fund our family friendly, fun activities and to maintain our beautiful entry drives.
+
+
This is a friendly reminder that the speed limit in the subdivision is 25. Drive safely and watch for children and pets.
+
+ Please note dues will be $30 for 2024.
+ +March/April 2020 (pdf)
+January 2020 (pdf)
+November/December 2019 (pdf)
+October 2019 (pdf)
+September 2019 (pdf)
+August 2019 (pdf)
+July 2019 (pdf)
+November/December 2016 (pdf)
+September/October 2016 (pdf)
+July/August 2016 (pdf) Inserts: Ice Cream Smash & Sandbagger Scramble
+May/June 2016 (pdf)
+March/April 2016 (pdf) Inserts: 2016 Membership Form & Block Captain Thank You
+January/February 2016 (pdf) Inserts: 2016 Membership Form & Holiday Doors
+November/December 2015 (pdf)
+September/October 2015 (pdf) Inserts: Garage Sale Flyer
+July/August 2015 (pdf) Inserts: Garage Sale Flyer & Garden Walk
+May/June 2015 (pdf)
+March/April 2015 (pdf) Inserts: Garage Sale Flyer & Golf Outing Flyer
+January/February 2015 (pdf)
+January 2015 - Annual Membership Form (pdf)
+November/December 2014 (pdf)
+September/October 2014 (pdf) Inserts: garage sale flyer
+July/August 2014 (pdf)
+May/June 2014 (pdf) Inserts: membership form & garage sale flyer
+March/April 2014 (pdf)
+Jan/Feb 2014 (pdf)
+November/December 2013 (pdf)
+September/October 2013 (pdf)
+ +July/August 2013 (pdf)
+ +May/June 2013 (pdf)
+ +March/April 2013 (pdf)
+ +January/February 2013 (pdf)
+Advertising Rates:
+
+ 1/4 page: $70
+ 1/2 page: $90
+
+ Full page: $175
Issue Deadlines:
+
+ July/August - July 3
+
+ September/October - September 1
+
+ November/December - November 1
+
+
Monica Dhillon |
+ President |
+ + |
Teresa Westfall |
+ 1st Vice President |
+ + |
Tom Hatting |
+ 2nd Vice President |
+ + |
Chris Cozart |
+ Treasurer |
+ + |
Anna Malik |
+ Secretary |
+ + |
Jill Feitl |
+ Web site |
+ + |
Wendy Peck |
+ Membership |
+ + |
Melissa Gonski |
+ Directory |
+ + |
Pat Hanika |
+ + | |
Stephanie Bakula |
+ Eblasts |
+ + |