+ filename, file_extension = os.path.splitext(filename)
+ return f"avatar/{instance.user.username}{file_extension.lower()}"
+
+
+# TODO: Add MentorManager
+class Mentor(CommonInfo):
+ user = models.ForeignKey(
+ CDCUser,
+ on_delete=models.CASCADE,
+ )
+ bio = models.TextField(
+ blank=True,
+ null=True,
+ )
+ is_active = models.BooleanField(
+ default=True,
+ )
+ background_check = models.BooleanField(
+ default=False,
+ )
+ is_public = models.BooleanField(
+ default=False,
+ )
+ avatar = StdImageField(
+ upload_to=generate_filename,
+ blank=True,
+ variations={
+ "thumbnail": {
+ "width": 500,
+ "height": 500,
+ "crop": True,
+ },
+ },
+ )
+ avatar_approved = models.BooleanField(
+ default=False,
+ )
+ birthday = models.DateField(
+ blank=False,
+ null=True,
+ )
+ gender = models.CharField(
+ max_length=255,
+ blank=False,
+ null=True,
+ )
+ race_ethnicity = models.ManyToManyField(
+ RaceEthnicity,
+ blank=False,
+ )
+ work_place = models.CharField(
+ max_length=255,
+ blank=True,
+ null=True,
+ )
+ phone = models.CharField(
+ max_length=255,
+ blank=True,
+ null=True,
+ )
+ home_address = models.CharField(
+ max_length=255,
+ blank=True,
+ null=True,
+ )
+
+ def __str__(self):
+ return self.full_name
+
+ @property
+ def first_name(self):
+ return self.user.first_name
+
+ @property
+ def last_name(self):
+ return self.user.last_name
+
+ @property
+ def full_name(self):
+ return f"{self.user.first_name} {self.user.last_name}"
+
+ @property
+ def email(self):
+ return self.user.email
+
+ def save(self, *args, **kwargs):
+ if self.pk is None:
+ NewMentorNotification(self).send()
+ else:
+ orig = Mentor.objects.get(pk=self.pk)
+ if orig.avatar != self.avatar:
+ self.avatar_approved = False
+
+ if self.background_check is True and orig.background_check != self.background_check:
+ NewMentorBgCheckNotification(self).send()
+
+ super(Mentor, self).save(*args, **kwargs)
+
+ def get_approve_avatar_url(self):
+ return reverse(
+ "mentor-approve-avatar",
+ args=[
+ str(self.id),
+ ],
+ )
+
+ def get_reject_avatar_url(self):
+ return reverse(
+ "mentor-reject-avatar",
+ args=[
+ str(self.id),
+ ],
+ )
+
+ def get_absolute_url(self):
+ return reverse(
+ "mentor-detail",
+ args=[
+ str(self.id),
+ ],
+ )
+
+ def get_avatar(self):
+ if (
+ self.avatar
+ and self.avatar.storage.exists(self.avatar.name)
+ and self.avatar.storage.exists(self.avatar.thumbnail.name)
+ ):
+ return self.avatar
+
+ # Gravatar
+ import hashlib
+ from urllib.parse import urlencode
+
+ # https://en.gravatar.com/site/implement/images/
+
+ email = self.email.encode("utf-8").lower()
+ email_encoded = hashlib.md5(email).hexdigest()
+
+ thumbnail_params = urlencode(
+ {
+ "d": "mp",
+ "r": "g",
+ "s": str(320),
+ }
+ )
+ full_params = urlencode(
+ {
+ "d": "mp",
+ "r": "g",
+ "s": str(500),
+ }
+ )
+ slug_url = f"https://www.gravatar.com/avatar/{email_encoded}"
+
+ avatar = {
+ "url": f"{slug_url}?{full_params}",
+ "thumbnail": {
+ "url": f"{slug_url}?{thumbnail_params}",
+ },
+ }
+
+ return avatar
diff --git a/coderdojochi/models/mentor_order.py b/coderdojochi/models/mentor_order.py
new file mode 100644
index 00000000..923c4d37
--- /dev/null
+++ b/coderdojochi/models/mentor_order.py
@@ -0,0 +1,64 @@
+import os
+
+from django.db import models
+
+from ..notifications import NewMentorOrderNotification
+from .common import CommonInfo
+
+
+class MentorOrder(CommonInfo):
+ from .mentor import Mentor
+ from .session import Session
+
+ mentor = models.ForeignKey(
+ Mentor,
+ on_delete=models.CASCADE,
+ )
+ session = models.ForeignKey(
+ Session,
+ on_delete=models.CASCADE,
+ )
+ is_active = models.BooleanField(
+ default=True,
+ )
+ ip = models.CharField(
+ max_length=255,
+ blank=True,
+ null=True,
+ )
+ check_in = models.DateTimeField(
+ blank=True,
+ null=True,
+ )
+ affiliate = models.CharField(
+ max_length=255,
+ blank=True,
+ null=True,
+ )
+ order_number = models.CharField(
+ max_length=255,
+ blank=True,
+ null=True,
+ )
+ week_reminder_sent = models.BooleanField(
+ default=False,
+ )
+ day_reminder_sent = models.BooleanField(
+ default=False,
+ )
+
+ def __str__(self):
+ return f"{self.mentor.full_name} | {self.session.course.title}"
+
+ def is_checked_in(self):
+ return self.check_in is not None
+
+ is_checked_in.boolean = True
+
+ def save(self, *args, **kwargs):
+ num_orders = MentorOrder.objects.filter(mentor__id=self.mentor.id).count()
+
+ if self.pk is None and num_orders == 0:
+ NewMentorOrderNotification(self).send()
+
+ super().save(*args, **kwargs)
diff --git a/coderdojochi/models/order.py b/coderdojochi/models/order.py
new file mode 100644
index 00000000..86c9a2c6
--- /dev/null
+++ b/coderdojochi/models/order.py
@@ -0,0 +1,73 @@
+from django.db import models
+
+from .common import CommonInfo
+
+
+class Order(CommonInfo):
+ from .guardian import Guardian
+ from .session import Session
+ from .student import Student
+
+ guardian = models.ForeignKey(
+ Guardian,
+ on_delete=models.CASCADE,
+ )
+ session = models.ForeignKey(
+ Session,
+ on_delete=models.CASCADE,
+ )
+ student = models.ForeignKey(
+ Student,
+ on_delete=models.CASCADE,
+ )
+ is_active = models.BooleanField(
+ default=True,
+ )
+ ip = models.CharField(
+ max_length=255,
+ blank=True,
+ null=True,
+ )
+ check_in = models.DateTimeField(
+ blank=True,
+ null=True,
+ )
+ alternate_guardian = models.CharField(
+ max_length=255,
+ blank=True,
+ null=True,
+ )
+ affiliate = models.CharField(
+ max_length=255,
+ blank=True,
+ null=True,
+ )
+ order_number = models.CharField(
+ max_length=255,
+ blank=True,
+ null=True,
+ )
+ week_reminder_sent = models.BooleanField(
+ default=False,
+ )
+ day_reminder_sent = models.BooleanField(
+ default=False,
+ )
+
+ def __str__(self):
+ return f"{self.student.full_name} | {self.session.course.title}"
+
+ def is_checked_in(self):
+ return self.check_in is not None
+
+ is_checked_in.boolean = True
+
+ def get_student_age(self):
+ return self.student.get_age(self.session.start_date)
+
+ get_student_age.short_description = "Age"
+
+ def get_student_gender(self):
+ return self.student.get_clean_gender().title()
+
+ get_student_gender.short_description = "Gender"
diff --git a/coderdojochi/models/race_ethnicity.py b/coderdojochi/models/race_ethnicity.py
new file mode 100644
index 00000000..ec86ca56
--- /dev/null
+++ b/coderdojochi/models/race_ethnicity.py
@@ -0,0 +1,19 @@
+from django.db import models
+
+from .common import CommonInfo
+
+
+class RaceEthnicity(CommonInfo):
+ race_ethnicity = models.CharField(
+ max_length=255,
+ )
+ is_visible = models.BooleanField(
+ default=False,
+ )
+
+ class Meta:
+ verbose_name = "Race/Ethnicity"
+ verbose_name_plural = "Race/Ethnicities"
+
+ def __str__(self):
+ return self.race_ethnicity
diff --git a/coderdojochi/models/session.py b/coderdojochi/models/session.py
new file mode 100644
index 00000000..b49ec70e
--- /dev/null
+++ b/coderdojochi/models/session.py
@@ -0,0 +1,324 @@
+from datetime import timedelta
+
+from django.core.validators import MaxValueValidator, MinValueValidator
+from django.db import models
+from django.urls.base import reverse
+from django.utils import formats
+from django.utils.functional import cached_property
+
+from .common import CommonInfo
+
+
+class Session(CommonInfo):
+ from .course import Course
+ from .location import Location
+ from .mentor import Mentor
+ from .student import Student
+
+ MALE = "male"
+ FEMALE = "female"
+
+ GENDER_LIMITATION_CHOICES = (
+ (MALE, "Male"),
+ (FEMALE, "Female"),
+ )
+
+ course = models.ForeignKey(
+ Course,
+ on_delete=models.CASCADE,
+ limit_choices_to={"is_active": True},
+ )
+ start_date = models.DateTimeField()
+ location = models.ForeignKey(
+ Location,
+ on_delete=models.CASCADE,
+ limit_choices_to={"is_active": True},
+ )
+ capacity = models.IntegerField(
+ default=20,
+ )
+ mentor_capacity = models.IntegerField(
+ blank=True,
+ null=True,
+ )
+ instructor = models.ForeignKey(
+ Mentor,
+ on_delete=models.CASCADE,
+ related_name="session_instructor",
+ limit_choices_to={"user__groups__name": "Instructor"},
+ )
+
+ # Pricing
+ cost = models.DecimalField(
+ max_digits=6,
+ decimal_places=2,
+ blank=True,
+ null=True,
+ )
+ minimum_cost = models.DecimalField(
+ max_digits=6,
+ decimal_places=2,
+ blank=True,
+ null=True,
+ )
+ maximum_cost = models.DecimalField(
+ max_digits=6,
+ decimal_places=2,
+ blank=True,
+ null=True,
+ )
+
+ # Extra
+ additional_info = models.TextField(blank=True, null=True, help_text="Basic HTML allowed")
+ waitlist_mentors = models.ManyToManyField(
+ Mentor,
+ blank=True,
+ related_name="session_waitlist_mentors",
+ )
+ waitlist_students = models.ManyToManyField(
+ Student,
+ blank=True,
+ related_name="session_waitlist_students",
+ )
+ external_enrollment_url = models.CharField(
+ max_length=255,
+ blank=True,
+ null=True,
+ help_text="When provided, local enrollment is disabled.",
+ )
+
+ is_active = models.BooleanField(
+ default=False,
+ help_text="Session is active.",
+ )
+ is_public = models.BooleanField(
+ default=False,
+ help_text="Session is a public session.",
+ )
+ password = models.CharField(
+ blank=True,
+ max_length=255,
+ )
+ partner_message = models.TextField(
+ blank=True,
+ )
+ announced_date_mentors = models.DateTimeField(
+ blank=True,
+ null=True,
+ )
+ announced_date_guardians = models.DateTimeField(
+ blank=True,
+ null=True,
+ )
+ image_url = models.CharField(
+ max_length=255,
+ blank=True,
+ null=True,
+ )
+ bg_image = models.ImageField(
+ blank=True,
+ null=True,
+ )
+ mentors_week_reminder_sent = models.BooleanField(
+ default=False,
+ )
+ mentors_day_reminder_sent = models.BooleanField(
+ default=False,
+ )
+ gender_limitation = models.CharField(
+ help_text="Limits the class to be only one gender.",
+ max_length=255,
+ choices=GENDER_LIMITATION_CHOICES,
+ blank=True,
+ null=True,
+ )
+ override_minimum_age_limitation = models.IntegerField(
+ "Min Age",
+ help_text="Only update this if different from the default.",
+ blank=True,
+ null=True,
+ validators=[MinValueValidator(0), MaxValueValidator(100)],
+ )
+ override_maximum_age_limitation = models.IntegerField(
+ "Max Age",
+ help_text="Only update this if different from the default.",
+ blank=True,
+ null=True,
+ validators=[MinValueValidator(0), MaxValueValidator(100)],
+ )
+ online_video_link = models.URLField(
+ "Online Video Link",
+ help_text="Zoom link with password.",
+ blank=True,
+ null=True,
+ )
+ online_video_meeting_id = models.CharField(
+ "Online Video Meeting ID",
+ help_text="XXX XXXX XXXX",
+ max_length=255,
+ blank=True,
+ null=True,
+ )
+ online_video_meeting_password = models.CharField(
+ "Online Video Meeting Password",
+ help_text="Plain text password shared by Zoom",
+ max_length=255,
+ blank=True,
+ null=True,
+ )
+ online_video_description = models.TextField(
+ "Online Video Description",
+ help_text="Information on how to connect to the video call. Basic HTML allowed.",
+ blank=True,
+ null=True,
+ )
+
+ # kept for older records
+ old_end_date = models.DateTimeField(
+ blank=True,
+ null=True,
+ )
+ old_mentor_start_date = models.DateTimeField(
+ blank=True,
+ null=True,
+ )
+ old_mentor_end_date = models.DateTimeField(
+ blank=True,
+ null=True,
+ )
+
+ @property
+ def end_date(self):
+ # Some records have a defined record with the end date,
+ # rather than use the course's duration.
+ # We're keeping this for old records.
+ if self.old_end_date:
+ return self.old_end_date
+
+ return self.start_date + self.course.duration
+
+ @property
+ def mentor_start_date(self):
+ # Some records have a defined record with the mentor start date,
+ # rather than do the math.
+ # We're keeping this for old records.
+ if self.old_mentor_start_date:
+ return self.old_mentor_start_date
+
+ return self.start_date - timedelta(hours=1)
+
+ @property
+ def mentor_end_date(self):
+ # Some records have a defined record with the mentor start date,
+ # rather than do the math.
+ # We're keeping this for old records.
+ if self.old_mentor_end_date:
+ return self.old_mentor_end_date
+
+ return self.end_date + timedelta(hours=1)
+
+ @property
+ def minimum_age(self):
+ if self.override_minimum_age_limitation is not None:
+ return self.override_minimum_age_limitation
+
+ return self.course.minimum_age
+
+ @property
+ def maximum_age(self):
+ if self.override_maximum_age_limitation is not None:
+ return self.override_maximum_age_limitation
+
+ return self.course.maximum_age
+
+ def __str__(self):
+ date = formats.date_format(self.start_date, "SHORT_DATETIME_FORMAT")
+ return f"{self.course.title} | {date}"
+
+ def save(self, *args, **kwargs):
+ if self.mentor_capacity is None:
+ self.mentor_capacity = int(self.capacity / 2)
+
+ super(Session, self).save(*args, **kwargs)
+
+ def get_absolute_url(self) -> str:
+ return reverse("session-detail", args=[str(self.id)])
+
+ def get_sign_up_url(self):
+ return reverse("session-sign-up", args=[str(self.id)])
+
+ def get_calendar_url(self):
+ return reverse("session-calendar", args=[str(self.id)])
+
+ def is_guardian_announced(self):
+ return self.announced_date_guardians is not None
+
+ is_guardian_announced.boolean = True
+ is_guardian_announced.short_description = "Is Announced"
+ is_guardian_announced.admin_order_field = "announced_date_guardians"
+
+ def get_mentor_orders(self):
+ from .mentor_order import MentorOrder
+
+ return MentorOrder.objects.filter(
+ session=self,
+ is_active=True,
+ ).order_by("mentor__user__last_name")
+
+ def get_checked_in_mentor_orders(self):
+ from .mentor_order import MentorOrder
+
+ return MentorOrder.objects.filter(session=self, is_active=True, check_in__isnull=False).order_by(
+ "mentor__user__last_name"
+ )
+
+ def get_current_orders(self, checked_in=None):
+ from .order import Order
+
+ if checked_in is not None:
+ if checked_in:
+ orders = (
+ Order.objects.filter(is_active=True, session=self)
+ .exclude(check_in=None)
+ .order_by("student__last_name")
+ )
+ else:
+ orders = Order.objects.filter(is_active=True, session=self, check_in=None).order_by(
+ "student__last_name"
+ )
+ else:
+ orders = Order.objects.filter(is_active=True, session=self).order_by("check_in", "student__last_name")
+
+ return orders
+
+ def get_active_student_count(self):
+ from .order import Order
+
+ return Order.objects.filter(is_active=True, session=self).values("student").count()
+
+ def get_checked_in_students(self):
+ from .order import Order
+
+ return Order.objects.filter(is_active=True, session=self).exclude(check_in=None).values("student")
+
+ def get_mentor_capacity(self):
+ if self.mentor_capacity:
+ return self.mentor_capacity
+ else:
+ return int(self.capacity / 2)
+
+
+class PartnerPasswordAccess(CommonInfo):
+ from .user import CDCUser
+
+ user = models.ForeignKey(
+ CDCUser,
+ on_delete=models.CASCADE,
+ )
+ session = models.ForeignKey(
+ Session,
+ on_delete=models.CASCADE,
+ )
+
+ class Meta:
+ db_table = "partner_password_access"
diff --git a/coderdojochi/models/student.py b/coderdojochi/models/student.py
new file mode 100644
index 00000000..88f6643f
--- /dev/null
+++ b/coderdojochi/models/student.py
@@ -0,0 +1,118 @@
+from django.db import models
+from django.utils import timezone
+
+from .common import CommonInfo
+from .race_ethnicity import RaceEthnicity
+
+
+class Student(CommonInfo):
+ from .guardian import Guardian
+
+ guardian = models.ForeignKey(
+ Guardian,
+ on_delete=models.CASCADE,
+ )
+ first_name = models.CharField(
+ max_length=255,
+ )
+ last_name = models.CharField(
+ max_length=255,
+ )
+ birthday = models.DateField()
+ gender = models.CharField(
+ max_length=255,
+ )
+ race_ethnicity = models.ManyToManyField(
+ RaceEthnicity,
+ blank=True,
+ )
+ school_name = models.CharField(
+ max_length=255,
+ null=True,
+ )
+ school_type = models.CharField(
+ max_length=255,
+ null=True,
+ )
+ medical_conditions = models.TextField(
+ blank=True,
+ null=True,
+ )
+ medications = models.TextField(
+ blank=True,
+ null=True,
+ )
+ photo_release = models.BooleanField(
+ "Photo Consent",
+ help_text=(
+ "I hereby give permission to We All Code to use "
+ "the student's image and/or likeness in promotional materials."
+ ),
+ default=False,
+ )
+ consent = models.BooleanField(
+ "General Consent",
+ help_text=("I hereby give consent for the student signed up " "above to participate in We All Code."),
+ default=False,
+ )
+ is_active = models.BooleanField(
+ default=True,
+ )
+
+ def __str__(self):
+ return f"{self.first_name} {self.last_name}"
+
+ @property
+ def full_name(self):
+ return f"{self.first_name} {self.last_name}"
+
+ def is_registered_for_session(self, session):
+ from .order import Order
+
+ try:
+ Order.objects.get(
+ is_active=True,
+ student=self,
+ session=session,
+ )
+ is_registered = True
+ except Exception:
+ is_registered = False
+
+ return is_registered
+
+ def get_age(self, date=timezone.now()):
+ return date.year - self.birthday.year - ((date.month, date.day) < (self.birthday.month, self.birthday.day))
+
+ get_age.short_description = "Age"
+
+ def get_clean_gender(self):
+ MALE = ["male", "m", "boy", "nino", "masculino"]
+ FEMALE = ["female", "f", "girl", "femail", "femal", "femenino"]
+
+ if self.gender.lower() in MALE:
+ return "male"
+ elif self.gender.lower() in FEMALE:
+ return "female"
+ else:
+ return "other"
+
+ get_clean_gender.short_description = "Clean Gender"
+
+ # returns True if the student age is between minimum_age and maximum_age
+ def is_within_age_range(self, minimum_age, maximum_age, date=timezone.now()):
+ age = self.get_age(date)
+
+ if age >= minimum_age and age <= maximum_age:
+ return True
+ else:
+ return False
+
+ def is_within_gender_limitation(self, limitation):
+ if limitation:
+ if self.get_clean_gender() in [limitation.lower(), "other"]:
+ return True
+ else:
+ return False
+ else:
+ return True
diff --git a/coderdojochi/models/user.py b/coderdojochi/models/user.py
new file mode 100644
index 00000000..5ffdac92
--- /dev/null
+++ b/coderdojochi/models/user.py
@@ -0,0 +1,40 @@
+from django.contrib.auth.models import AbstractUser
+from django.db import models
+from django.urls import reverse
+from django.utils import timezone
+from django.utils.functional import cached_property
+
+
+class CDCUser(AbstractUser):
+
+ MENTOR = "mentor"
+ GUARDIAN = "guardian"
+
+ ROLE_CHOICES = [
+ (MENTOR, "mentor"),
+ (GUARDIAN, "guardian"),
+ ]
+
+ role = models.CharField(
+ choices=ROLE_CHOICES,
+ max_length=10,
+ blank=True,
+ null=True,
+ )
+
+ admin_notes = models.TextField(
+ blank=True,
+ null=True,
+ )
+
+ @cached_property
+ def name(self):
+ return f"{self.first_name} {self.last_name}"
+
+ def save(self, *args, **kwargs):
+ if self.pk is None:
+ self.last_login = timezone.now()
+ super(CDCUser, self).save(*args, **kwargs)
+
+ def get_absolute_url(self):
+ return reverse("account_home")
diff --git a/coderdojochi/notifications.py b/coderdojochi/notifications.py
index 28b2064c..999b60db 100644
--- a/coderdojochi/notifications.py
+++ b/coderdojochi/notifications.py
@@ -1,104 +1,62 @@
import logging
-import requests
from django.conf import settings
+import requests
+
logger = logging.getLogger(__name__)
class SlackNotification:
- DEFAULT_PAYLOAD = {
- "channel": settings.SLACK_ALERTS_CHANNEL,
- "text": "IMPLEMENT_ME"
- }
+ DEFAULT_PAYLOAD = {"channel": settings.SLACK_ALERTS_CHANNEL, "text": "IMPLEMENT_ME"}
def __init__(self):
self.payload = DEFAULT_PAYLOAD
def send(self):
- res = requests.post(settings.SLACK_WEBHOOK_URL, json={
- **self.DEFAULT_PAYLOAD,
- **self.payload
- })
+ res = requests.post(settings.SLACK_WEBHOOK_URL, json={**self.DEFAULT_PAYLOAD, **self.payload})
if res.status_code != requests.codes.ok:
- logger.error({
- "msg": "Unable to send Slack notification",
- "error": res.content
- })
+ logger.error({"msg": "Unable to send Slack notification", "error": res.content})
class NewMentorNotification(SlackNotification):
def __init__(self, mentor):
self.payload = {
"blocks": [
- {
- "type": "divider"
- },
- {
- "type": "section",
- "text": {
- "text": "👋 New mentor signup!",
- "type": "mrkdwn"
- }
- },
+ {"type": "divider"},
+ {"type": "section", "text": {"text": "👋 New mentor signup!", "type": "mrkdwn"}},
{
"type": "section",
"fields": [
- {
- "type": "mrkdwn",
- "text": f"*Name*: \n{mentor.user.name}"
- },
- {
- "type": "mrkdwn",
- "text": f"*Email*: \n{mentor.user.email}"
- }
- ]
- }
+ {"type": "mrkdwn", "text": f"*Name*: \n{mentor.full_name}"},
+ {"type": "mrkdwn", "text": f"*Email*: \n{mentor.email}"},
+ ],
+ },
]
}
class NewMentorOrderNotification(SlackNotification):
def __init__(self, mentor_order):
- name = mentor_order.mentor.user.name
- email = mentor_order.mentor.user.email
+ name = mentor_order.mentor.full_name
+ email = mentor_order.mentor.email
location = mentor_order.session.location.name
start_date = mentor_order.session.start_date.strftime("%Y-%m-%d")
self.payload = {
"blocks": [
- {
- "type": "divider"
- },
- {
- "type": "section",
- "text": {
- "type": "mrkdwn",
- "text": "🏫 New mentor enrollment!"
- }
- },
+ {"type": "divider"},
+ {"type": "section", "text": {"type": "mrkdwn", "text": "🏫 New mentor enrollment!"}},
{
"type": "section",
"fields": [
- {
- "type": "mrkdwn",
- "text": f"*Name*: \n{name}\n"
- },
- {
- "type": "mrkdwn",
- "text": f"*Email*: \n{email}\n"
- },
- {
- "type": "mrkdwn",
- "text": f"*Location*: \n{location}"
- },
- {
- "type": "mrkdwn",
- "text": f"*Date*: \n{start_date}"
- }
- ]
- }
+ {"type": "mrkdwn", "text": f"*Name*: \n{name}\n"},
+ {"type": "mrkdwn", "text": f"*Email*: \n{email}\n"},
+ {"type": "mrkdwn", "text": f"*Location*: \n{location}"},
+ {"type": "mrkdwn", "text": f"*Date*: \n{start_date}"},
+ ],
+ },
]
}
@@ -107,28 +65,14 @@ class NewMentorBgCheckNotification(SlackNotification):
def __init__(self, mentor):
self.payload = {
"blocks": [
- {
- "type": "divider"
- },
- {
- "type": "section",
- "text": {
- "type": "mrkdwn",
- "text": "✅ New mentor background check!"
- }
- },
+ {"type": "divider"},
+ {"type": "section", "text": {"type": "mrkdwn", "text": "✅ New mentor background check!"}},
{
"type": "section",
"fields": [
- {
- "type": "mrkdwn",
- "text": f"*Name*: \n{mentor.user.name}"
- },
- {
- "type": "mrkdwn",
- "text": f"*Email*: \n{mentor.user.email}"
- }
- ]
- }
+ {"type": "mrkdwn", "text": f"*Name*: \n{mentor.user.name}"},
+ {"type": "mrkdwn", "text": f"*Email*: \n{mentor.user.email}"},
+ ],
+ },
]
}
diff --git a/coderdojochi/old_views.py b/coderdojochi/old_views.py
index e096cda5..80695a5c 100644
--- a/coderdojochi/old_views.py
+++ b/coderdojochi/old_views.py
@@ -1,8 +1,7 @@
-import calendar
import logging
import operator
from collections import Counter
-from datetime import date, timedelta
+from datetime import timedelta
from functools import reduce
from django.conf import settings
@@ -10,29 +9,17 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ObjectDoesNotExist
-from django.core.mail import EmailMultiAlternatives, get_connection
from django.db.models import Case, Count, IntegerField, When
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils import timezone
-from django.utils.html import strip_tags
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt
-from django.views.generic import TemplateView
import arrow
-from dateutil.relativedelta import relativedelta
-from icalendar import Calendar, Event, vText
-
-from coderdojochi.forms import (
- CDCModelForm,
- ContactForm,
- DonationForm,
- GuardianForm,
- MentorForm,
- StudentForm,
-)
+
+from coderdojochi.forms import DonationForm, StudentForm
from coderdojochi.models import (
Donation,
Equipment,
@@ -43,7 +30,6 @@
Mentor,
MentorOrder,
Order,
- PartnerPasswordAccess,
Session,
Student,
)
@@ -59,41 +45,34 @@ def home(request, template_name="home.html"):
upcoming_classes = Session.objects.filter(
is_active=True,
start_date__gte=timezone.now(),
- ).order_by('start_date')
+ ).order_by("start_date")
- if (
- not request.user.is_authenticated or
- not request.user.role == 'mentor'
- ):
+ if not request.user.is_authenticated or not request.user.role == "mentor":
upcoming_classes = upcoming_classes.filter(is_public=True)
upcoming_classes = upcoming_classes[:3]
- return render(request, template_name, {
- 'upcoming_classes': upcoming_classes
- })
+ return render(request, template_name, {"upcoming_classes": upcoming_classes})
def volunteer(request, template_name="volunteer.html"):
- mentors = Mentor.objects.select_related('user').filter(
- is_active=True,
- is_public=True,
- background_check=True,
- avatar_approved=True,
- ).annotate(
- session_count=Count('mentororder')
- ).order_by('-user__role', '-session_count')
-
- upcoming_meetings = Meeting.objects.filter(
- is_active=True,
- is_public=True,
- end_date__gte=timezone.now()
- ).order_by('start_date')[:3]
+ mentors = (
+ Mentor.objects.select_related("user")
+ .filter(
+ is_active=True,
+ is_public=True,
+ background_check=True,
+ avatar_approved=True,
+ )
+ .annotate(session_count=Count("mentororder"))
+ .order_by("-user__role", "-session_count")
+ )
+
+ upcoming_meetings = Meeting.objects.filter(is_active=True, is_public=True, end_date__gte=timezone.now()).order_by(
+ "start_date"
+ )[:3]
- return render(request, template_name, {
- 'mentors': mentors,
- 'upcoming_meetings': upcoming_meetings
- })
+ return render(request, template_name, {"mentors": mentors, "upcoming_meetings": upcoming_meetings})
@login_required
@@ -101,14 +80,9 @@ def mentor_approve_avatar(request, pk=None):
mentor = get_object_or_404(Mentor, id=pk)
if not request.user.is_staff:
- messages.error(
- request,
- 'You do not have permissions to moderate content.'
- )
+ messages.error(request, "You do not have permissions to moderate content.")
- return redirect(
- f"{reverse('account_login')}?next={mentor.get_approve_avatar_url()}"
- )
+ return redirect(f"{reverse('account_login')}?next={mentor.get_approve_avatar_url()}")
mentor.avatar_approved = True
mentor.save()
@@ -116,23 +90,18 @@ def mentor_approve_avatar(request, pk=None):
if mentor.background_check:
messages.success(
request,
- f"{mentor.user.first_name} {mentor.user.last_name}'s avatar approved and their account is now public."
+ f"{mentor.full_name}'s avatar approved and their account is now public.",
)
- return redirect(
- f"{reverse('mentors')}{mentor.id}"
- )
+ return redirect(f"{reverse('mentors')}{mentor.id}")
else:
messages.success(
request,
- (
- f"{mentor.user.first_name}{mentor.user.last_name}'s avatar approved but they have yet "
- f"to fill out the 'background search' form."
- )
+ f"{mentor.full_name}'s avatar approved but they have yet to fill out the 'background search' form.",
)
- return redirect('mentors')
+ return redirect("mentors")
@login_required
@@ -140,57 +109,45 @@ def mentor_reject_avatar(request, pk=None):
mentor = get_object_or_404(Mentor, id=pk)
if not request.user.is_staff:
- messages.error(
- request,
- 'You do not have permissions to moderate content.'
- )
+ messages.error(request, "You do not have permissions to moderate content.")
- return redirect(
- f"{reverse('account_login')}?next={mentor.get_reject_avatar_url()}"
- )
+ return redirect(f"{reverse('account_login')}?next={mentor.get_reject_avatar_url()}")
mentor.avatar_approved = False
mentor.save()
email(
- subject='Your We All Code avatar...',
- template_name='class-announcement-mentor',
+ subject="Your We All Code avatar...",
+ template_name="mentor_reject_avatar",
merge_global_data={
- 'site_url': settings.SITE_URL,
+ "site_url": settings.SITE_URL,
},
- recipients=[mentor.user.email],
+ recipients=[mentor.email],
)
messages.warning(
request,
- (
- f"{mentor.user.first_name} {mentor.user.last_name}'s avatar rejected and their account "
- f"is no longer public. An email notice has been sent to the mentor."
- )
+ f"{mentor.full_name}'s avatar rejected and their account is no longer public. An email notice has been sent to the mentor.",
)
- return redirect('mentors')
+ return redirect("mentors")
@login_required
-def student_detail(
- request,
- student_id=False,
- template_name="student-detail.html"
-):
+def student_detail(request, student_id=False, template_name="student_detail.html"):
access = True
- if request.user.role == 'guardian' and student_id:
+ if request.user.role == "guardian" and student_id:
# for the specific student redirect to admin page
try:
student = Student.objects.get(id=student_id, is_active=True)
except ObjectDoesNotExist:
- return redirect('account_home')
+ return redirect("account_home")
try:
guardian = Guardian.objects.get(user=request.user, is_active=True)
except ObjectDoesNotExist:
- return redirect('account_home')
+ return redirect("account_home")
if not student.guardian == guardian:
access = False
@@ -200,222 +157,130 @@ def student_detail(
access = False
if not access:
- return redirect('account_home')
- messages.error(
- request,
- 'You do not have permissions to edit this student.'
- )
+ return redirect("account_home")
+ messages.error(request, "You do not have permissions to edit this student.")
- if request.method == 'POST':
- if 'delete' in request.POST:
+ if request.method == "POST":
+ if "delete" in request.POST:
student.is_active = False
student.save()
- messages.success(
- request,
- f"Student \"{student.first_name} {student.last_name}\" Deleted."
- )
- return redirect('account_home')
+ messages.success(request, f'Student "{student.full_name}" Deleted.')
+ return redirect("account_home")
form = StudentForm(request.POST, instance=student)
if form.is_valid():
form.save()
- messages.success(request, 'Student Updated.')
- return redirect('account_home')
+ messages.success(request, "Student Updated.")
+ return redirect("account_home")
- return render(
- request,
- template_name,
- {
- 'form': form
- }
- )
+ return render(request, template_name, {"form": form})
@login_required
def cdc_admin(request, template_name="admin.html"):
if not request.user.is_staff:
- messages.error(
- request,
- 'You do not have permission to access this page.'
- )
- return redirect('weallcode-home')
-
- sessions = Session.objects.select_related().annotate(
- num_orders=Count(
- 'order'
- ),
-
- num_attended=Count(
- Case(
- When(
- order__check_in__isnull=False,
- then=1
- )
- )
- ),
-
- is_future=Case(
- When(
- start_date__gte=timezone.now(),
- then=1
+ messages.error(request, "You do not have permission to access this page.")
+ return redirect("weallcode-home")
+
+ sessions = (
+ Session.objects.select_related()
+ .annotate(
+ num_orders=Count("order"),
+ num_attended=Count(Case(When(order__check_in__isnull=False, then=1))),
+ is_future=Case(
+ When(start_date__gte=timezone.now(), then=1),
+ default=0,
+ output_field=IntegerField(),
),
- default=0,
- output_field=IntegerField(),
)
- ).order_by(
- '-start_date'
+ .order_by("-start_date")
)
- meetings = Meeting.objects.select_related().annotate(
- num_orders=Count(
- 'meetingorder'
- ),
-
- num_attended=Count(
- Case(
- When(
- meetingorder__check_in__isnull=False,
- then=1
- )
- )
- ),
-
- is_future=Case(
- When(
- end_date__gte=timezone.now(),
- then=1
+ meetings = (
+ Meeting.objects.select_related()
+ .annotate(
+ num_orders=Count("meetingorder"),
+ num_attended=Count(Case(When(meetingorder__check_in__isnull=False, then=1))),
+ is_future=Case(
+ When(end_date__gte=timezone.now(), then=1),
+ default=0,
+ output_field=IntegerField(),
),
- default=0,
- output_field=IntegerField(),
)
-
- ).order_by(
- '-start_date'
+ .order_by("-start_date")
)
orders = Order.objects.select_related()
total_past_orders = orders.filter(is_active=True)
total_past_orders_count = total_past_orders.count()
- total_checked_in_orders = orders.filter(
- is_active=True,
- check_in__isnull=False
- )
+ total_checked_in_orders = orders.filter(is_active=True, check_in__isnull=False)
total_checked_in_orders_count = total_checked_in_orders.count()
# Genders
- gender_count = list(
- Counter(
- e.student.get_clean_gender() for e in total_checked_in_orders
- ).items()
- )
- gender_count = sorted(
- list(dict(gender_count).items()),
- key=operator.itemgetter(1)
- )
+ gender_count = list(Counter(e.student.get_clean_gender() for e in total_checked_in_orders).items())
+ gender_count = sorted(list(dict(gender_count).items()), key=operator.itemgetter(1))
# Ages
- ages = sorted(
- list(
- e.student.get_age(e.session.start_date) for e in total_checked_in_orders
- )
- )
- age_count = sorted(
- list(dict(
- list(
- Counter(ages).items()
- )
- ).items()),
- key=operator.itemgetter(0)
- )
+ ages = sorted(list(e.student.get_age(e.session.start_date) for e in total_checked_in_orders))
+ age_count = sorted(list(dict(list(Counter(ages).items())).items()), key=operator.itemgetter(0))
# Average Age
- average_age = int(
- round(
- sum(ages) / float(len(ages))
- )
- )
+ average_age = int(round(sum(ages) / float(len(ages))))
return render(
request,
template_name,
{
- 'age_count': age_count,
- 'average_age': average_age,
- 'gender_count': gender_count,
- 'meetings': meetings,
+ "age_count": age_count,
+ "average_age": average_age,
+ "gender_count": gender_count,
+ "meetings": meetings,
# 'past_meetings_count': past_meetings_count,
# 'past_sessions': past_sessions,
# 'past_sessions_count': past_sessions_count,
- 'sessions': sessions,
- 'total_checked_in_orders_count': total_checked_in_orders_count,
- 'total_past_orders_count': total_past_orders_count,
+ "sessions": sessions,
+ "total_checked_in_orders_count": total_checked_in_orders_count,
+ "total_past_orders_count": total_past_orders_count,
# 'upcoming_meetings': upcoming_meetings,
# 'upcoming_meetings_count': upcoming_meetings_count,
# 'upcoming_sessions': upcoming_sessions,
# 'upcoming_sessions_count': upcoming_sessions_count,
- }
+ },
)
@login_required
@never_cache
-def session_stats(request, pk, template_name="session-stats.html"):
+def session_stats(request, pk, template_name="session_stats.html"):
if not request.user.is_staff:
- messages.error(
- request,
- 'You do not have permission to access this page.'
- )
- return redirect('weallcode-home')
+ messages.error(request, "You do not have permission to access this page.")
+ return redirect("weallcode-home")
session_obj = get_object_or_404(Session, pk=pk)
- current_orders_checked_in = session_obj.get_current_orders(
- checked_in=True
- )
+ current_orders_checked_in = session_obj.get_current_orders(checked_in=True)
- students_checked_in = current_orders_checked_in.values('student')
+ students_checked_in = current_orders_checked_in.values("student")
if students_checked_in:
attendance_percentage = round(
- (
- float(current_orders_checked_in.count()) /
- float(session_obj.get_current_students().count())
- ) * 100
+ (float(current_orders_checked_in.count()) / float(session_obj.get_active_student_count())) * 100
)
else:
attendance_percentage = False
# Genders
- gender_count = list(
- Counter(
- e.student.get_clean_gender()
- for e in session_obj.get_current_orders()
- ).items()
- )
+ gender_count = list(Counter(e.student.get_clean_gender() for e in session_obj.get_current_orders()).items())
- gender_count = sorted(
- list(dict(gender_count).items()),
- key=operator.itemgetter(1)
- )
+ gender_count = sorted(list(dict(gender_count).items()), key=operator.itemgetter(1))
# Ages
- ages = sorted(
- list(
- e.student.get_age(e.session.start_date) for e in session_obj.get_current_orders()
- )
- )
+ ages = sorted(list(e.student.get_age(e.session.start_date) for e in session_obj.get_current_orders()))
- age_count = sorted(
- list(dict(
- list(
- Counter(ages).items()
- )
- ).items()),
- key=operator.itemgetter(1)
- )
+ age_count = sorted(list(dict(list(Counter(ages).items())).items()), key=operator.itemgetter(1))
# Average Age
average_age = False
@@ -424,198 +289,123 @@ def session_stats(request, pk, template_name="session-stats.html"):
for order in current_orders_checked_in:
student_ages.append(order.student.get_age(order.session.start_date))
- average_age = (
- reduce(
- lambda x, y: x + y,
- student_ages
- ) /
- len(student_ages)
- )
+ average_age = reduce(lambda x, y: x + y, student_ages) / len(student_ages)
return render(
request,
template_name,
{
- 'session': session_obj,
- 'students_checked_in': students_checked_in,
- 'attendance_percentage': attendance_percentage,
- 'average_age': average_age,
- 'age_count': age_count,
- 'gender_count': gender_count
- }
+ "session": session_obj,
+ "students_checked_in": students_checked_in,
+ "attendance_percentage": attendance_percentage,
+ "average_age": average_age,
+ "age_count": age_count,
+ "gender_count": gender_count,
+ },
)
@login_required
@never_cache
-def session_check_in(request, pk, template_name="session-check-in.html"):
+def session_check_in(request, pk, template_name="session_check_in.html"):
if not request.user.is_staff:
- messages.error(
- request,
- 'You do not have permission to access this page.'
- )
+ messages.error(request, "You do not have permission to access this page.")
- return redirect('weallcode-home')
+ return redirect("weallcode-home")
- if request.method == 'POST':
- if 'order_id' in request.POST:
- order = get_object_or_404(
- Order,
- id=request.POST['order_id']
- )
+ if request.method == "POST":
+ if "order_id" in request.POST:
+ order = get_object_or_404(Order, id=request.POST["order_id"])
if order.check_in:
order.check_in = None
else:
order.check_in = timezone.now()
- if (
- f"{order.guardian.user.first_name} {order.guardian.user.last_name}" !=
- request.POST['order_alternate_guardian']
- ):
- order.alternate_guardian = request.POST[
- 'order_alternate_guardian'
- ]
+ if f"{order.guardian.full_name}" != request.POST["order_alternate_guardian"]:
+ order.alternate_guardian = request.POST["order_alternate_guardian"]
order.save()
else:
- messages.error(request, 'Invalid Order')
+ messages.error(request, "Invalid Order")
# Get current session
session = get_object_or_404(Session, pk=pk)
# Active Session
- active_session = True if timezone.now() < session.end_date else False
+ if timezone.now() < session.end_date:
+ active_session = True
+ else:
+ active_session = False
# get the orders
- orders = Order.objects.select_related().filter(session_id=pk).annotate(
- num_attended=Count(
- Case(
- When(
- student__order__check_in__isnull=False,
- then=1
- )
- )
- ),
- num_missed=Count(
- Case(
- When(
- student__order__check_in__isnull=True,
- then=1
- )
- )
+ orders = (
+ Order.objects.select_related()
+ .filter(session_id=pk)
+ .annotate(
+ num_attended=Count(Case(When(student__order__check_in__isnull=False, then=1))),
+ num_missed=Count(Case(When(student__order__check_in__isnull=True, then=1))),
)
)
if active_session:
- active_orders = orders.filter(
- is_active=True
- ).order_by(
- 'student__first_name'
- )
+ active_orders = orders.filter(is_active=True).order_by("student__first_name")
else:
- active_orders = orders.filter(
- is_active=True,
- check_in__isnull=False
- ).order_by(
- 'student__first_name'
- )
+ active_orders = orders.filter(is_active=True, check_in__isnull=False).order_by("student__first_name")
- inactive_orders = orders.filter(
- is_active=False
- ).order_by('-updated_at')
+ inactive_orders = orders.filter(is_active=False).order_by("-updated_at")
- no_show_orders = orders.filter(
- is_active=True,
- check_in__isnull=True
- )
+ no_show_orders = orders.filter(is_active=True, check_in__isnull=True)
- checked_in_orders = orders.filter(
- is_active=True,
- check_in__isnull=False
- )
+ checked_in_orders = orders.filter(is_active=True, check_in__isnull=False)
# Genders
gender_count = sorted(
- list(dict(
- list(
- Counter(
- e.student.get_clean_gender() for e in active_orders
- ).items()
- )
- ).items()),
- key=operator.itemgetter(1)
+ list(dict(list(Counter(e.student.get_clean_gender() for e in active_orders).items())).items()),
+ key=operator.itemgetter(1),
)
# Ages
- ages = sorted(
- list(
- e.student.get_age(e.session.start_date) for e in active_orders
- )
- )
-
- age_count = sorted(
- list(dict(
- list(
- Counter(ages).items()
- )
- ).items()),
- key=operator.itemgetter(0)
- )
+ ages = sorted(list(e.student.get_age(e.session.start_date) for e in active_orders))
- # age_count = sorted(
- # dict(
- # list(
- # Counter(ages).items()
- # )
- # ).items(),
- # key=operator.itemgetter(1),
- # reverse=True
- # )
+ age_count = sorted(list(dict(list(Counter(ages).items())).items()), key=operator.itemgetter(0))
# Average Age
- average_age = int(
- round(
- sum(ages) / float(len(ages))
- )
- ) if orders and ages else 0
+ if orders and ages:
+ average_age = int(round(sum(ages) / float(len(ages))))
+ else:
+ average_age = 0
return render(
request,
template_name,
{
- 'session': session,
- 'active_session': active_session,
- 'active_orders': active_orders,
- 'inactive_orders': inactive_orders,
- 'no_show_orders': no_show_orders,
- 'gender_count': gender_count,
- 'age_count': age_count,
- 'average_age': average_age,
- 'checked_in_orders': checked_in_orders,
- }
+ "session": session,
+ "active_session": active_session,
+ "active_orders": active_orders,
+ "inactive_orders": inactive_orders,
+ "no_show_orders": no_show_orders,
+ "gender_count": gender_count,
+ "age_count": age_count,
+ "average_age": average_age,
+ "checked_in_orders": checked_in_orders,
+ },
)
@login_required
@never_cache
-def session_check_in_mentors(request, pk, template_name="session-check-in-mentors.html"):
+def session_check_in_mentors(request, pk, template_name="session_check_in_mentors.html"):
if not request.user.is_staff:
- messages.error(
- request,
- 'You do not have permission to access this page.'
- )
- return redirect('weallcode-home')
+ messages.error(request, "You do not have permission to access this page.")
+ return redirect("weallcode-home")
- if request.method == 'POST':
- if 'order_id' in request.POST:
- order = get_object_or_404(
- MentorOrder,
- id=request.POST['order_id']
- )
+ if request.method == "POST":
+ if "order_id" in request.POST:
+ order = get_object_or_404(MentorOrder, id=request.POST["order_id"])
if order.check_in:
order.check_in = None
@@ -624,131 +414,86 @@ def session_check_in_mentors(request, pk, template_name="session-check-in-mentor
order.save()
else:
- messages.error(
- request,
- 'Invalid Order'
- )
+ messages.error(request, "Invalid Order")
session = get_object_or_404(Session, pk=pk)
# Active Session
- active_session = True if timezone.now() < session.end_date else False
+ if timezone.now() < session.end_date:
+ active_session = True
+ else:
+ active_session = False
# get the orders
orders = MentorOrder.objects.select_related().filter(session_id=pk)
if active_session:
- active_orders = orders.filter(
- is_active=True
- ).order_by(
- 'mentor__user__first_name'
- )
+ active_orders = orders.filter(is_active=True).order_by("mentor__user__first_name")
else:
- active_orders = orders.filter(
- is_active=True,
- check_in__isnull=False
- ).order_by(
- 'mentor__user__first_name'
- )
+ active_orders = orders.filter(is_active=True, check_in__isnull=False).order_by("mentor__user__first_name")
- inactive_orders = orders.filter(
- is_active=False
- ).order_by('-updated_at')
+ inactive_orders = orders.filter(is_active=False).order_by("-updated_at")
- no_show_orders = orders.filter(
- is_active=True,
- check_in__isnull=True
- )
+ no_show_orders = orders.filter(is_active=True, check_in__isnull=True)
- checked_in_orders = orders.filter(
- is_active=True,
- check_in__isnull=False
- )
+ checked_in_orders = orders.filter(is_active=True, check_in__isnull=False)
return render(
request,
template_name,
{
- 'session': session,
- 'active_session': active_session,
- 'active_orders': active_orders,
- 'inactive_orders': inactive_orders,
- 'no_show_orders': no_show_orders,
- 'checked_in_orders': checked_in_orders,
- }
+ "session": session,
+ "active_session": active_session,
+ "active_orders": active_orders,
+ "inactive_orders": inactive_orders,
+ "no_show_orders": no_show_orders,
+ "checked_in_orders": checked_in_orders,
+ },
)
@login_required
@never_cache
-def session_donations(request, pk, template_name="session-donations.html"):
+def session_donations(request, pk, template_name="session_donations.html"):
# TODO: we should really turn this into a decorator
if not request.user.is_staff:
- messages.error(
- request,
- 'You do not have permission to access this page.'
- )
- return redirect('account_home')
+ messages.error(request, "You do not have permission to access this page.")
+ return redirect("account_home")
session = get_object_or_404(Session, pk=pk)
- default_form = DonationForm(initial={'session': session})
- default_form.fields['user'].queryset = User.objects.filter(
- id__in=Order.objects.filter(
- session=session
- ).values_list(
- 'guardian__user__id', flat=True
- )
+ default_form = DonationForm(initial={"session": session})
+ default_form.fields["user"].queryset = User.objects.filter(
+ id__in=Order.objects.filter(session=session).values_list("guardian__user__id", flat=True)
)
form = default_form
donations = Donation.objects.filter(session=session)
- if request.method == 'POST':
+ if request.method == "POST":
form = DonationForm(request.POST)
if form.is_valid():
form.save()
form = default_form
- messages.success(
- request,
- 'Donation added!'
- )
+ messages.success(request, "Donation added!")
- return render(
- request,
- template_name,
- {
- 'form': form,
- 'session': session,
- 'donations': donations
- }
- )
+ return render(request, template_name, {"form": form, "session": session, "donations": donations})
@login_required
@never_cache
-def meeting_check_in(
- request,
- meeting_id,
- template_name="meeting-check-in.html"
-):
+def meeting_check_in(request, meeting_id, template_name="meeting_check_in.html"):
if not request.user.is_staff:
- messages.error(
- request,
- 'You do not have permission to access this page.'
- )
- return redirect('account_home')
+ messages.error(request, "You do not have permission to access this page.")
+ return redirect("account_home")
- if request.method == 'POST':
- if 'order_id' in request.POST:
- order = get_object_or_404(
- MeetingOrder,
- id=request.POST['order_id']
- )
+ if request.method == "POST":
+ if "order_id" in request.POST:
+ order = get_object_or_404(MeetingOrder, id=request.POST["order_id"])
if order.check_in:
order.check_in = None
@@ -757,69 +502,55 @@ def meeting_check_in(
order.save()
else:
- messages.error(request, 'Invalid Order')
+ messages.error(request, "Invalid Order")
- orders = MeetingOrder.objects.select_related().filter(
- meeting=meeting_id
- ).order_by(
- 'mentor__user__first_name'
- )
+ orders = MeetingOrder.objects.select_related().filter(meeting=meeting_id).order_by("mentor__user__first_name")
- active_orders = orders.filter(
- is_active=True
- )
+ active_orders = orders.filter(is_active=True)
- inactive_orders = orders.filter(
- is_active=False
- )
+ inactive_orders = orders.filter(is_active=False)
- checked_in = orders.filter(
- is_active=True,
- check_in__isnull=False
- )
+ checked_in = orders.filter(is_active=True, check_in__isnull=False)
return render(
request,
template_name,
{
- 'active_orders': active_orders,
- 'inactive_orders': inactive_orders,
- 'checked_in': checked_in,
- }
+ "active_orders": active_orders,
+ "inactive_orders": inactive_orders,
+ "checked_in": checked_in,
+ },
)
@never_cache
def session_announce_mentors(request, pk):
if not request.user.is_staff:
- messages.error(
- request,
- 'You do not have permission to access this page.'
- )
- return redirect('home')
+ messages.error(request, "You do not have permission to access this page.")
+ return redirect("home")
session_obj = get_object_or_404(Session, pk=pk)
if not session_obj.announced_date_mentors:
merge_data = {}
merge_global_data = {
- 'class_code': session_obj.course.code,
- 'class_title': session_obj.course.title,
- 'class_description': session_obj.course.description,
- 'class_start_date': arrow.get(session_obj.mentor_start_date).to('local').format('dddd, MMMM D, YYYY'),
- 'class_start_time': arrow.get(session_obj.mentor_start_date).to('local').format('h:mma'),
- 'class_end_date': arrow.get(session_obj.end_date).to('local').format('dddd, MMMM D, YYYY'),
- 'class_end_time': arrow.get(session_obj.end_date).to('local').format('h:mma'),
- 'minimum_age': session_obj.minimum_age,
- 'maximum_age': session_obj.maximum_age,
- 'class_location_name': session_obj.location.name,
- 'class_location_address': session_obj.location.address,
- 'class_location_city': session_obj.location.city,
- 'class_location_state': session_obj.location.state,
- 'class_location_zip': session_obj.location.zip,
- 'class_additional_info': session_obj.additional_info,
- 'class_url': f"{settings.SITE_URL}{session_obj.get_absolute_url()}",
- 'class_calendar_url': f"{settings.SITE_URL}{session_obj.get_calendar_url()}",
+ "class_code": session_obj.course.code,
+ "class_title": session_obj.course.title,
+ "class_description": session_obj.course.description,
+ "class_start_date": arrow.get(session_obj.mentor_start_date).to("local").format("dddd, MMMM D, YYYY"),
+ "class_start_time": arrow.get(session_obj.mentor_start_date).to("local").format("h:mma"),
+ "class_end_date": arrow.get(session_obj.end_date).to("local").format("dddd, MMMM D, YYYY"),
+ "class_end_time": arrow.get(session_obj.end_date).to("local").format("h:mma"),
+ "minimum_age": session_obj.minimum_age,
+ "maximum_age": session_obj.maximum_age,
+ "class_location_name": session_obj.location.name,
+ "class_location_address": session_obj.location.address,
+ "class_location_city": session_obj.location.city,
+ "class_location_state": session_obj.location.state,
+ "class_location_zip": session_obj.location.zip,
+ "class_additional_info": session_obj.additional_info,
+ "class_url": f"{settings.SITE_URL}{session_obj.get_absolute_url()}",
+ "class_calendar_url": f"{settings.SITE_URL}{session_obj.get_calendar_url()}",
}
recipients = []
@@ -830,70 +561,61 @@ def session_announce_mentors(request, pk):
)
for mentor in mentors:
- recipients.append(mentor.user.email)
- merge_data[mentor.user.email] = {
- 'first_name': mentor.user.first_name,
- 'last_name': mentor.user.last_name,
+ recipients.append(mentor.email)
+ merge_data[mentor.email] = {
+ "first_name": mentor.first_name,
+ "last_name": mentor.last_name,
}
email(
- subject='New We All Code class date announced! Come mentor!',
- template_name='class-announcement-mentor',
+ subject="New We All Code class date announced! Come mentor!",
+ template_name="class_announcement_mentor",
merge_data=merge_data,
merge_global_data=merge_global_data,
recipients=recipients,
- preheader='Help us make a huge difference! A brand new class was just announced.',
+ preheader="Help us make a huge difference! A brand new class was just announced.",
unsub_group_id=settings.SENDGRID_UNSUB_CLASSANNOUNCE,
)
session_obj.announced_date_mentors = timezone.now()
session_obj.save()
- messages.success(
- request,
- f'Session announced to {mentors.count()} mentors.'
- )
+ messages.success(request, f"Session announced to {mentors.count()} mentors.")
else:
- messages.warning(
- request,
- f'Session already announced.'
- )
+ messages.warning(request, f"Session already announced.")
- return redirect('cdc-admin')
+ return redirect("cdc-admin")
@never_cache
def session_announce_guardians(request, pk):
if not request.user.is_staff:
- messages.error(
- request,
- 'You do not have permission to access this page.'
- )
- return redirect('home')
+ messages.error(request, "You do not have permission to access this page.")
+ return redirect("home")
session_obj = get_object_or_404(Session, pk=pk)
if not session_obj.announced_date_guardians:
merge_data = {}
merge_global_data = {
- 'class_code': session_obj.course.code,
- 'class_title': session_obj.course.title,
- 'class_description': session_obj.course.description,
- 'class_start_date': arrow.get(session_obj.start_date).to('local').format('dddd, MMMM D, YYYY'),
- 'class_start_time': arrow.get(session_obj.start_date).to('local').format('h:mma'),
- 'class_end_date': arrow.get(session_obj.end_date).to('local').format('dddd, MMMM D, YYYY'),
- 'class_end_time': arrow.get(session_obj.end_date).to('local').format('h:mma'),
- 'minimum_age': session_obj.minimum_age,
- 'maximum_age': session_obj.maximum_age,
- 'class_location_name': session_obj.location.name,
- 'class_location_address': session_obj.location.address,
- 'class_location_city': session_obj.location.city,
- 'class_location_state': session_obj.location.state,
- 'class_location_zip': session_obj.location.zip,
- 'class_additional_info': session_obj.additional_info,
- 'class_url': f"{settings.SITE_URL}{session_obj.get_absolute_url()}",
- 'class_calendar_url': f"{settings.SITE_URL}{session_obj.get_calendar_url()}",
+ "class_code": session_obj.course.code,
+ "class_title": session_obj.course.title,
+ "class_description": session_obj.course.description,
+ "class_start_date": arrow.get(session_obj.start_date).to("local").format("dddd, MMMM D, YYYY"),
+ "class_start_time": arrow.get(session_obj.start_date).to("local").format("h:mma"),
+ "class_end_date": arrow.get(session_obj.end_date).to("local").format("dddd, MMMM D, YYYY"),
+ "class_end_time": arrow.get(session_obj.end_date).to("local").format("h:mma"),
+ "minimum_age": session_obj.minimum_age,
+ "maximum_age": session_obj.maximum_age,
+ "class_location_name": session_obj.location.name,
+ "class_location_address": session_obj.location.address,
+ "class_location_city": session_obj.location.city,
+ "class_location_state": session_obj.location.state,
+ "class_location_zip": session_obj.location.zip,
+ "class_additional_info": session_obj.additional_info,
+ "class_url": f"{settings.SITE_URL}{session_obj.get_absolute_url()}",
+ "class_calendar_url": f"{settings.SITE_URL}{session_obj.get_calendar_url()}",
}
recipients = []
@@ -903,15 +625,15 @@ def session_announce_guardians(request, pk):
)
for guardian in guardians:
- recipients.append(guardian.user.email)
- merge_data[guardian.user.email] = {
- 'first_name': guardian.user.first_name,
- 'last_name': guardian.user.last_name,
+ recipients.append(guardian.email)
+ merge_data[guardian.email] = {
+ "first_name": guardian.first_name,
+ "last_name": guardian.last_name,
}
email(
- subject='New We All Code class date announced!',
- template_name='class-announcement-guardian',
+ subject="New We All Code class date announced!",
+ template_name="class_announcement_guardian",
merge_data=merge_data,
merge_global_data=merge_global_data,
recipients=recipients,
@@ -922,18 +644,12 @@ def session_announce_guardians(request, pk):
session_obj.announced_date_guardians = timezone.now()
session_obj.save()
- messages.success(
- request,
- f'Session announced to {guardians.count()} guardians!'
- )
+ messages.success(request, f"Session announced to {guardians.count()} guardians!")
else:
- messages.warning(
- request,
- 'Session already announced.'
- )
+ messages.warning(request, "Session already announced.")
- return redirect('cdc-admin')
+ return redirect("cdc-admin")
@csrf_exempt
@@ -943,36 +659,27 @@ def check_system(request):
runUpdate = True
responseString = ""
cmdString = (
- 'sh -c "$(curl -fsSL '
- 'https://raw.githubusercontent.com/CoderDojoChi'
- '/linux-update/master/update.sh)"'
+ 'sh -c "$(curl -fsSL ' "https://raw.githubusercontent.com/CoderDojoChi" '/linux-update/master/update.sh)"'
)
halfday = timedelta(hours=12)
# halfday = timedelta(seconds=15)
- if (
- Session.objects.filter(
- is_active=True,
- start_date__lte=timezone.now(),
- ).count()
- ):
+ if Session.objects.filter(
+ is_active=True,
+ start_date__lte=timezone.now(),
+ ).count():
runUpdate = False
# uuid is posted from the computer using a bash script
# see:
# https://raw.githubusercontent.com/CoderDojoChi
# /linux-update/master/etc/init.d/coderdojochi-phonehome
- uuid = request.POST.get('uuid')
+ uuid = request.POST.get("uuid")
if uuid:
equipmentType = EquipmentType.objects.get(name="Laptop")
if equipmentType:
- equipment, created = Equipment.objects.get_or_create(
- uuid=uuid,
- defaults={
- 'equipment_type': equipmentType
- }
- )
+ equipment, created = Equipment.objects.get_or_create(uuid=uuid, defaults={"equipment_type": equipmentType})
# check for blank values of last_system_update.
# If blank, assume we need to run it
@@ -980,12 +687,8 @@ def check_system(request):
equipment.force_update_on_next_boot = True
# do we need to update?
- if (
- runUpdate and
- (
- equipment.force_update_on_next_boot or
- (timezone.now() - equipment.last_system_update > halfday)
- )
+ if runUpdate and (
+ equipment.force_update_on_next_boot or (timezone.now() - equipment.last_system_update > halfday)
):
responseString = cmdString
equipment.last_system_update = timezone.now()
diff --git a/coderdojochi/settings.py b/coderdojochi/settings.py
index f1eb93c8..ead206f8 100644
--- a/coderdojochi/settings.py
+++ b/coderdojochi/settings.py
@@ -30,25 +30,25 @@
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env(
- 'SECRET_KEY',
- default='!!!SET SECRET_KEY!!!',
+ "SECRET_KEY",
+ default="!!!SET SECRET_KEY!!!",
)
# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = env.bool('DEBUG', default=False)
+DEBUG = env.bool("DEBUG", default=False)
# reCAPTCHA
-RECAPTCHA_PUBLIC_KEY = env('RECAPTCHA_PUBLIC_KEY', default='')
-RECAPTCHA_PRIVATE_KEY = env('RECAPTCHA_PRIVATE_KEY', default='')
-RECAPTCHA_REQUIRED_SCORE = env('RECAPTCHA_REQUIRED_SCORE', default=0.85)
+RECAPTCHA_PUBLIC_KEY = env("RECAPTCHA_PUBLIC_KEY", default="")
+RECAPTCHA_PRIVATE_KEY = env("RECAPTCHA_PRIVATE_KEY", default="")
+RECAPTCHA_REQUIRED_SCORE = env("RECAPTCHA_REQUIRED_SCORE", default=0.85)
# SECURITY
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-redirect
-SECURE_SSL_REDIRECT = env.bool('DJANGO_SECURE_SSL_REDIRECT', default=True)
+SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=True)
if SECURE_SSL_REDIRECT:
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header
- SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
+ SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure
SESSION_COOKIE_SECURE = True
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly
@@ -62,99 +62,92 @@
# set this to 60 seconds first and then to 518400 once you prove the former works
SECURE_HSTS_SECONDS = 518400
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains
- SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool('DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS', default=True)
+ SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool("DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True)
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload
- SECURE_HSTS_PRELOAD = env.bool('DJANGO_SECURE_HSTS_PRELOAD', default=True)
+ SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True)
# https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff
- SECURE_CONTENT_TYPE_NOSNIFF = env.bool('DJANGO_SECURE_CONTENT_TYPE_NOSNIFF', default=True)
+ SECURE_CONTENT_TYPE_NOSNIFF = env.bool("DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True)
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter
SECURE_BROWSER_XSS_FILTER = True
# https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options
- X_FRAME_OPTIONS = 'DENY'
+ X_FRAME_OPTIONS = "DENY"
# Application definition
INSTALLED_APPS = [
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.sites',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'django.contrib.humanize',
- 'django.contrib.admin',
-
- 'django.contrib.redirects',
- 'django.contrib.sitemaps',
-
+ "django.contrib.auth",
+ "django.contrib.contenttypes",
+ "django.contrib.sessions",
+ "django.contrib.sites",
+ "django.contrib.messages",
+ "django.contrib.staticfiles",
+ "django.contrib.humanize",
+ "django.contrib.admin",
+ "django.contrib.redirects",
+ "django.contrib.sitemaps",
# vendor
-
- # allauth
- 'allauth',
- 'allauth.account',
- 'allauth.socialaccount',
- 'allauth.socialaccount.providers.facebook',
- 'allauth.socialaccount.providers.google',
-
- 'bootstrap3',
- 'django_cleanup',
- 'anymail',
- 'html5',
- 'loginas',
- 'stdimage',
- 'import_export',
- 'django_nose',
- 'meta',
- 'captcha',
-
+ "allauth",
+ "allauth.account",
+ "allauth.socialaccount",
+ "allauth.socialaccount.providers.facebook",
+ "allauth.socialaccount.providers.google",
+ "bootstrap3",
+ "django_cleanup",
+ "anymail",
+ "html5",
+ "loginas",
+ "stdimage",
+ "import_export",
+ "django_nose",
+ "meta",
+ "captcha",
# apps
- 'accounts',
- 'coderdojochi',
- 'weallcode',
+ "accounts",
+ "coderdojochi",
+ "weallcode",
]
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.contrib.redirects.middleware.RedirectFallbackMiddleware',
+ "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.contrib.redirects.middleware.RedirectFallbackMiddleware",
]
-ROOT_URLCONF = 'coderdojochi.urls'
+ROOT_URLCONF = "coderdojochi.urls"
TEMPLATES = [
{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [
- os.path.join(BASE_DIR, 'accounts/templates/'),
- os.path.join(BASE_DIR, 'weallcode/templates/'),
- os.path.join(BASE_DIR, 'coderdojochi/templates/'),
- os.path.join(BASE_DIR, 'coderdojochi/templates/dashboard/'),
- os.path.join(BASE_DIR, 'coderdojochi/emailtemplates/'),
- os.path.join(BASE_DIR, 'coderdojochi/mentors/templates'),
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [
+ os.path.join(BASE_DIR, "accounts/templates/"),
+ os.path.join(BASE_DIR, "weallcode/templates/"),
+ os.path.join(BASE_DIR, "coderdojochi/templates/"),
+ os.path.join(BASE_DIR, "coderdojochi/templates/dashboard/"),
+ os.path.join(BASE_DIR, "coderdojochi/emailtemplates/"),
+ os.path.join(BASE_DIR, "coderdojochi/mentors/templates"),
],
- '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',
+ "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",
# Project
- 'coderdojochi.context_processors.main_config_processor',
+ "coderdojochi.context_processors.main_config_processor",
],
- 'debug': DEBUG,
+ "debug": DEBUG,
},
},
]
-WSGI_APPLICATION = 'coderdojochi.wsgi.application'
+WSGI_APPLICATION = "coderdojochi.wsgi.application"
# Database
@@ -164,20 +157,20 @@
DATABASES = {"default": env.db()}
else:
DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.postgresql_psycopg2',
- 'NAME': os.environ.get('POSTGRES_DB'),
- 'USER': os.environ.get('POSTGRES_USER'),
- 'PASSWORD': os.environ.get('POSTGRES_PASSWORD'),
- 'HOST': os.environ.get('POSTGRES_HOST'),
- 'PORT': os.environ.get('POSTGRES_PORT'),
- }
+ "default": {
+ "ENGINE": "django.db.backends.postgresql_psycopg2",
+ "NAME": os.environ.get("POSTGRES_DB"),
+ "USER": os.environ.get("POSTGRES_USER"),
+ "PASSWORD": os.environ.get("POSTGRES_PASSWORD"),
+ "HOST": os.environ.get("POSTGRES_HOST"),
+ "PORT": os.environ.get("POSTGRES_PORT"),
}
+ }
DATABASES["default"]["ATOMIC_REQUESTS"] = True
# Change 'default' database configuration with $DATABASE_URL.
-DATABASES['default'].update(dj_database_url.config(conn_max_age=500, ssl_require=True))
+DATABASES["default"].update(dj_database_url.config(conn_max_age=500, ssl_require=True))
# Password validation
@@ -185,16 +178,16 @@
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",
},
]
@@ -202,51 +195,51 @@
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/
-LANGUAGE_CODE = 'en-us'
-TIME_ZONE = 'America/Chicago'
+LANGUAGE_CODE = "en-us"
+TIME_ZONE = "America/Chicago"
USE_I18N = True
USE_L10N = True
USE_TZ = True
SITE_ID = 1
-SITE_NAME = 'We All Code'
-SITE_URL = env('SITE_URL', default=None)
+SITE_NAME = "We All Code"
+SITE_URL = env("SITE_URL", default=None)
# Honor the 'X-Forwarded-Proto' header for request.is_secure()
-SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
+SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# Allow all host headers
-ALLOWED_HOSTS = ['*']
+ALLOWED_HOSTS = ["*"]
if DEBUG:
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
# STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
- STATIC_URL = '/static/'
+ STATIC_URL = "/static/"
# Extra places for collectstatic to find static files.
STATICFILES_DIRS = [
- os.path.join(PROJECT_ROOT, 'static'),
- os.path.join(BASE_DIR, 'accounts/static'),
- os.path.join(BASE_DIR, 'weallcode/static'),
+ os.path.join(PROJECT_ROOT, "static"),
+ os.path.join(BASE_DIR, "accounts/static"),
+ os.path.join(BASE_DIR, "weallcode/static"),
]
# Media files
- MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
- MEDIA_URL = '/media/'
+ MEDIA_ROOT = os.path.join(BASE_DIR, "media")
+ MEDIA_URL = "/media/"
else:
# STORAGES
# ------------------------------------------------------------------------------
# https://django-storages.readthedocs.io/en/latest/#installation
- INSTALLED_APPS += ['storages'] # noqa F405
+ INSTALLED_APPS += ["storages"] # noqa F405
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
- AWS_ACCESS_KEY_ID = env('AWS_ACCESS_KEY_ID')
+ AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID")
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
- AWS_SECRET_ACCESS_KEY = env('AWS_SECRET_ACCESS_KEY')
+ AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY")
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
- AWS_STORAGE_BUCKET_NAME = env('AWS_STORAGE_BUCKET_NAME')
+ AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
AWS_AUTO_CREATE_BUCKET = True
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
@@ -255,22 +248,22 @@
_AWS_EXPIRY = 60 * 60 * 24 * 7
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
AWS_S3_OBJECT_PARAMETERS = {
- 'CacheControl': f'max-age={_AWS_EXPIRY}, s-maxage={_AWS_EXPIRY}, must-revalidate',
+ "CacheControl": f"max-age={_AWS_EXPIRY}, s-maxage={_AWS_EXPIRY}, must-revalidate",
}
# STATIC
# ------------------------
- STATICFILES_STORAGE = 'coderdojochi.settings.StaticRootS3BotoStorage'
- STATIC_URL = f'https://s3.amazonaws.com/{AWS_STORAGE_BUCKET_NAME}/static/'
+ STATICFILES_STORAGE = "coderdojochi.settings.StaticRootS3BotoStorage"
+ STATIC_URL = f"https://s3.amazonaws.com/{AWS_STORAGE_BUCKET_NAME}/static/"
# MEDIA
# ------------------------------------------------------------------------------
# region http://stackoverflow.com/questions/10390244/
- from storages.backends.s3boto3 import S3Boto3Storage, SpooledTemporaryFile # noqa E402
from django.contrib.staticfiles.storage import ManifestFilesMixin
- # ManifestFilesSafeMixin = lambda: ManifestFilesMixin(manifest_strict=False)
+ from storages.backends.s3boto3 import S3Boto3Storage, SpooledTemporaryFile # noqa E402
+ # ManifestFilesSafeMixin = lambda: ManifestFilesMixin(manifest_strict=False)
# Taken from an issue in django-storages:
# https://github.com/jschneier/django-storages/issues/382#issuecomment-377174808
class CustomS3Storage(ManifestFilesMixin, S3Boto3Storage):
@@ -295,80 +288,75 @@ def _save_content(self, obj, content, parameters):
if not content_autoclose.closed:
content_autoclose.close()
- def StaticRootS3BotoStorage(): return CustomS3Storage(location='static')
+ def StaticRootS3BotoStorage():
+ return CustomS3Storage(location="static")
- def MediaRootS3BotoStorage(): return S3Boto3Storage(location='media', file_overwrite=False)
+ def MediaRootS3BotoStorage():
+ return S3Boto3Storage(location="media", file_overwrite=False)
- DEFAULT_FILE_STORAGE = 'coderdojochi.settings.MediaRootS3BotoStorage'
- MEDIA_URL = f'https://s3.amazonaws.com/{AWS_STORAGE_BUCKET_NAME}/media/'
+ DEFAULT_FILE_STORAGE = "coderdojochi.settings.MediaRootS3BotoStorage"
+ MEDIA_URL = f"https://s3.amazonaws.com/{AWS_STORAGE_BUCKET_NAME}/media/"
# endregion
AUTHENTICATION_BACKENDS = (
- 'django.contrib.auth.backends.ModelBackend',
-
+ "django.contrib.auth.backends.ModelBackend",
# `allauth` specific authentication methods, such as login by e-mail
- 'allauth.account.auth_backends.AuthenticationBackend',
+ "allauth.account.auth_backends.AuthenticationBackend",
)
-AUTH_USER_MODEL = 'coderdojochi.CDCUser'
+AUTH_USER_MODEL = "coderdojochi.CDCUser"
# Django Meta
-META_SITE_PROTOCOL = env('META_SITE_PROTOCOL', default='https')
-META_SITE_DOMAIN = env('META_SITE_DOMAIN', default='www.weallcode.org')
+META_SITE_PROTOCOL = env("META_SITE_PROTOCOL", default="https")
+META_SITE_DOMAIN = env("META_SITE_DOMAIN", default="www.weallcode.org")
META_SITE_NAME = SITE_NAME
META_USE_OG_PROPERTIES = True
META_USE_TWITTER_PROPERTIES = True
META_USE_SCHEMAORG_PROPERTIES = True
# META_USE_TITLE_TAG = True
-META_TWITTER_SITE = env('META_TWITTER_SITE', default='@weallcode')
-META_FB_APPID = env('META_SITE_DOMAIN', default='1454178301519376')
-META_INCLUDE_KEYWORDS = env.list('META_INCLUDE_KEYWORDS', default=[
- 'stem',
- 'code',
- 'coding',
- 'kids',
- 'chicago',
- 'chicago coding'
-])
-DEFAULT_META_TITLE = env('DEFAULT_META_TITLE', default='')
+META_TWITTER_SITE = env("META_TWITTER_SITE", default="@weallcode")
+META_FB_APPID = env("META_SITE_DOMAIN", default="1454178301519376")
+META_INCLUDE_KEYWORDS = env.list(
+ "META_INCLUDE_KEYWORDS", default=["stem", "code", "coding", "kids", "chicago", "chicago coding"]
+)
+DEFAULT_META_TITLE = env("DEFAULT_META_TITLE", default="")
# django allauth
-LOGIN_REDIRECT_URL = '/account'
-LOGIN_URL = '/account/login'
+LOGIN_REDIRECT_URL = "/account"
+LOGIN_URL = "/account/login"
ACCOUNT_EMAIL_REQUIRED = True
-ACCOUNT_AUTHENTICATION_METHOD = 'email'
+ACCOUNT_AUTHENTICATION_METHOD = "email"
ACCOUNT_USERNAME_REQUIRED = False
-ACCOUNT_SIGNUP_FORM_CLASS = 'coderdojochi.forms.SignupForm'
-SOCIALACCOUNT_ADAPTER = 'coderdojochi.social_account_adapter.SocialAccountAdapter'
+ACCOUNT_SIGNUP_FORM_CLASS = "coderdojochi.forms.SignupForm"
+SOCIALACCOUNT_ADAPTER = "coderdojochi.social_account_adapter.SocialAccountAdapter"
# Email
ANYMAIL = {
- 'SENDGRID_API_KEY': env('SENDGRID_API_KEY'),
+ "SENDGRID_API_KEY": env("SENDGRID_API_KEY"),
}
-EMAIL_BACKEND = 'anymail.backends.sendgrid.EmailBackend'
-DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL')
-CONTACT_EMAIL = env('CONTACT_EMAIL')
-SENDGRID_UNSUB_CLASSANNOUNCE = env.int('SENDGRID_UNSUB_CLASSANNOUNCE')
+EMAIL_BACKEND = "anymail.backends.sendgrid.EmailBackend"
+DEFAULT_FROM_EMAIL = env("DEFAULT_FROM_EMAIL")
+CONTACT_EMAIL = env("CONTACT_EMAIL")
+SENDGRID_UNSUB_CLASSANNOUNCE = env.int("SENDGRID_UNSUB_CLASSANNOUNCE")
# Slack
-SLACK_WEBHOOK_URL = env('SLACK_WEBHOOK_URL')
-SLACK_ALERTS_CHANNEL = env('SLACK_ALERTS_CHANNEL', default=None)
+SLACK_WEBHOOK_URL = env("SLACK_WEBHOOK_URL")
+SLACK_ALERTS_CHANNEL = env("SLACK_ALERTS_CHANNEL", default=None)
# Sentry
-SENTRY_DSN = env('SENTRY_DSN')
+SENTRY_DSN = env("SENTRY_DSN")
sentry_sdk.init(
dsn=SENTRY_DSN,
integrations=[DjangoIntegration()],
-
# If you wish to associate users to errors (assuming you are using
# django.contrib.auth) you may enable sending PII data.
- send_default_pii=True
+ send_default_pii=True,
)
@@ -380,18 +368,14 @@ def custom_show_toolbar(request):
DEBUG_TOOLBAR_PATCH_SETTINGS = False
- INSTALLED_APPS += (
- 'debug_toolbar',
- )
+ INSTALLED_APPS += ("debug_toolbar",)
- MIDDLEWARE += (
- 'debug_toolbar.middleware.DebugToolbarMiddleware',
- )
+ MIDDLEWARE += ("debug_toolbar.middleware.DebugToolbarMiddleware",)
DEBUG_TOOLBAR_CONFIG = {
- 'SHOW_TOOLBAR_CALLBACK': custom_show_toolbar,
- 'TAG': 'div',
- 'ENABLE_STACKTRACES': True,
+ "SHOW_TOOLBAR_CALLBACK": custom_show_toolbar,
+ "TAG": "div",
+ "ENABLE_STACKTRACES": True,
}
diff --git a/coderdojochi/signals_handlers.py b/coderdojochi/signals_handlers.py
index 68b7baa2..7ee53c33 100644
--- a/coderdojochi/signals_handlers.py
+++ b/coderdojochi/signals_handlers.py
@@ -4,11 +4,8 @@
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.signals import pre_save
from django.dispatch import receiver
-from django.shortcuts import get_object_or_404
-import arrow
-
-from coderdojochi.models import Donation, Mentor
+from coderdojochi.models import Mentor
from coderdojochi.util import email
@@ -27,21 +24,21 @@ def avatar_updated_handler(sender, instance, **kwargs):
instance.avatar_approved = False
img = MIMEImage(instance.avatar.read())
- img.add_header('Content-Id', 'avatar')
+ img.add_header("Content-Id", "avatar")
img.add_header("Content-Disposition", "inline", filename="avatar")
email(
- subject=f"{instance.user.first_name} {instance.user.last_name} | Mentor Avatar Changed",
- template_name='avatar-changed-mentor',
+ subject=f"{instance.full_name} | Mentor Avatar Changed",
+ template_name="avatar_changed_mentor",
merge_global_data={
- 'first_name': instance.user.first_name,
- 'last_name': instance.user.last_name,
- 'image': 'avatar',
- 'approve_url': f"{settings.SITE_URL}{instance.get_approve_avatar_url()}",
- 'reject_url': f"{settings.SITE_URL}{instance.get_reject_avatar_url()}",
+ "first_name": instance.first_name,
+ "last_name": instance.last_name,
+ "image": "avatar",
+ "approve_url": f"{settings.SITE_URL}{instance.get_approve_avatar_url()}",
+ "reject_url": f"{settings.SITE_URL}{instance.get_reject_avatar_url()}",
},
recipients=[settings.CONTACT_EMAIL],
- preheader='Mentor Avatar Changed',
+ preheader="Mentor Avatar Changed",
attachments=[img],
- mixed_subtype='related',
+ mixed_subtype="related",
)
diff --git a/coderdojochi/social_account_adapter.py b/coderdojochi/social_account_adapter.py
index f052a56a..37580e23 100644
--- a/coderdojochi/social_account_adapter.py
+++ b/coderdojochi/social_account_adapter.py
@@ -1,8 +1,10 @@
-from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from django.contrib.auth import get_user_model
+from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
+
User = get_user_model()
+
class SocialAccountAdapter(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
"""
@@ -24,7 +26,7 @@ def pre_social_login(self, request, sociallogin):
# e.g. facebook accounts
# with mobile numbers only, but allauth takes care of this case
# so just ignore it
- if 'email' not in sociallogin.account.extra_data:
+ if "email" not in sociallogin.account.extra_data:
return
# check if given email address already exists.
@@ -32,7 +34,7 @@ def pre_social_login(self, request, sociallogin):
try:
# email = sociallogin.account.extra_data['email'].lower()
# email_address = EmailAddress.objects.get(email__iexact=email)
- email = sociallogin.account.extra_data['email'].lower()
+ email = sociallogin.account.extra_data["email"].lower()
user = User.objects.get(email__iexact=email)
# if it does not, let allauth take care of this new social account
diff --git a/coderdojochi/templates/coderdojochi/mentor_detail.html b/coderdojochi/templates/coderdojochi/mentor_detail.html
index 2971fc72..5150c5f8 100644
--- a/coderdojochi/templates/coderdojochi/mentor_detail.html
+++ b/coderdojochi/templates/coderdojochi/mentor_detail.html
@@ -1,28 +1,28 @@
{% extends "coderdojochi/_base.html" %}
{% load humanize %}
-{% block title %}{{ mentor.user.first_name }} {{ mentor.user.last_name }} | {{ block.super }}{% endblock %}
+{% block title %}{{ mentor.full_name }} | {{ block.super }}{% endblock %}
{% block body_class %}page-mentor-detail{% endblock %}
{% block contained_content %}
-
+
All Mentors
- {{ mentor.user.first_name }} {{ mentor.user.last_name }}
-
+
{{ mentor.full_name }}
+
-
{{ mentor.user.first_name }} {{ mentor.user.last_name }}
+
{{ mentor.full_name }}
{{ mentor.bio }}
-
+
Joined: {{ mentor.user.date_joined|naturalday|capfirst }}
{% if user.is_staff %}
diff --git a/coderdojochi/templates/coderdojochi/mentor_list.html b/coderdojochi/templates/coderdojochi/mentor_list.html
index 9b46cd8a..f2d00e5c 100644
--- a/coderdojochi/templates/coderdojochi/mentor_list.html
+++ b/coderdojochi/templates/coderdojochi/mentor_list.html
@@ -12,9 +12,9 @@
{{ mentor.full_name }}
{% endfor %}
diff --git a/coderdojochi/templates/dashboard/_admin-base.html b/coderdojochi/templates/dashboard/_admin_base.html
similarity index 100%
rename from coderdojochi/templates/dashboard/_admin-base.html
rename to coderdojochi/templates/dashboard/_admin_base.html
diff --git a/coderdojochi/templates/dashboard/admin.html b/coderdojochi/templates/dashboard/admin.html
index 2cbfdf21..d76de3c6 100644
--- a/coderdojochi/templates/dashboard/admin.html
+++ b/coderdojochi/templates/dashboard/admin.html
@@ -1,4 +1,4 @@
-{% extends "_admin-base.html" %}
+{% extends "_admin_base.html" %}
{% load i18n coderdojochi_extras %}
diff --git a/coderdojochi/templates/dashboard/meeting-check-in.html b/coderdojochi/templates/dashboard/meeting_check_in.html
similarity index 97%
rename from coderdojochi/templates/dashboard/meeting-check-in.html
rename to coderdojochi/templates/dashboard/meeting_check_in.html
index 20ae4a87..913b84db 100644
--- a/coderdojochi/templates/dashboard/meeting-check-in.html
+++ b/coderdojochi/templates/dashboard/meeting_check_in.html
@@ -1,4 +1,4 @@
-{% extends "_admin-base.html" %}
+{% extends "_admin_base.html" %}
{% load i18n humanize coderdojochi_extras %}
@@ -159,7 +159,7 @@ Attending {{ orders.count }}
{% csrf_token %}
{{ forloop.counter|stringformat:"02d" }}
- {{ order.mentor.user.first_name }} {{ order.mentor.user.last_name }}
+ {{ order.mentor.first_name }} {{ order.mentor.last_name }}
{% if not order.check_in %}
Check In
@@ -198,7 +198,7 @@ Cancelled {{ inactive_orders.count }}
{{ forloop.counter|stringformat:"02d" }}
- {{ order.mentor.user.first_name }} {{ order.mentor.user.last_name }}
+ {{ order.mentor.first_name }} {{ order.mentor.last_name }}
{{ order.updated_at }}
{% endfor %}
diff --git a/coderdojochi/templates/dashboard/session-check-in.html b/coderdojochi/templates/dashboard/session_check_in.html
similarity index 96%
rename from coderdojochi/templates/dashboard/session-check-in.html
rename to coderdojochi/templates/dashboard/session_check_in.html
index 78c88ad4..105feb18 100644
--- a/coderdojochi/templates/dashboard/session-check-in.html
+++ b/coderdojochi/templates/dashboard/session_check_in.html
@@ -1,4 +1,4 @@
-{% extends "_admin-base.html" %}
+{% extends "_admin_base.html" %}
{% load i18n humanize coderdojochi_extras %}
@@ -210,16 +210,16 @@ Attended Students {{ active_orders.count }
{{ order.student | student_age:session.start_date }}
{% if active_session %}
-
+
{% else %}
{% if order.alternate_guardian %}
{{ order.alternate_guardian }}
{% else %}
- {{ order.guardian.user.first_name }} {{ order.guardian.user.last_name }}
+ {{ order.guardian.full_name }}
{% endif %}
{% endif %}
- {{ order.guardian.user.email }}
+ {{ order.guardian.email }}
{{ order.guardian.phone }}
{{ order.student.school_name }}
@@ -273,7 +273,7 @@ No Shows {{ no_show_orders.count }}
{{ order.student.first_name }} {{ order.student.last_name }}
{{ order.student | student_age:session.start_date }}
- {{ order.guardian.user.first_name }} {{ order.guardian.user.last_name }}
+ {{ order.guardian.full_name }}
{{ order.updated_at }}
{{ order.num_attended }}
{{ order.num_missed }}
@@ -312,8 +312,8 @@ Cancelled Tickets {{ inactive_orders.count
{{ forloop.counter|stringformat:"02d" }}
{{ order.student.first_name }} {{ order.student.last_name }}
{{ order.student | student_age:session.start_date }}
- {{ order.guardian.user.first_name }} {{ order.guardian.user.last_name }}
- {{ order.guardian.user.email }}
+ {{ order.guardian.full_name }}
+ {{ order.guardian.email }}
{{ order.guardian.phone }}
{{ order.student.school_name }}
{{ order.updated_at }}
diff --git a/coderdojochi/templates/dashboard/session-check-in-mentors.html b/coderdojochi/templates/dashboard/session_check_in_mentors.html
similarity index 95%
rename from coderdojochi/templates/dashboard/session-check-in-mentors.html
rename to coderdojochi/templates/dashboard/session_check_in_mentors.html
index 71f2714b..c105d7a9 100644
--- a/coderdojochi/templates/dashboard/session-check-in-mentors.html
+++ b/coderdojochi/templates/dashboard/session_check_in_mentors.html
@@ -1,4 +1,4 @@
-{% extends "_admin-base.html" %}
+{% extends "_admin_base.html" %}
{% load i18n humanize coderdojochi_extras %}
@@ -86,9 +86,9 @@ Attended Mentors {{ active_orders.count }}
{{ forloop.counter|stringformat:"02d" }}
- {{ order.mentor.user.first_name }} {{ order.mentor.user.last_name }}
+ {{ order.mentor.first_name }} {{ order.mentor.last_name }}
- {{ order.mentor.user.email }}
+ {{ order.mentor.email }}
{{ order.mentor.phone }}
-
-
@@ -145,8 +145,8 @@ No Shows {{ no_show_orders.count }}
{% for order in no_show_orders %}
{{ forloop.counter|stringformat:"02d" }}
- {{ order.mentor.user.first_name }} {{ order.mentor.user.last_name }}
- {{ order.mentor.user.email }}
+ {{ order.mentor.first_name }} {{ order.mentor.last_name }}
+ {{ order.mentor.email }}
{{ order.mentor.phone }}
-
-
@@ -180,7 +180,7 @@ Cancelled Tickets {{ inactive_orders.count
{{ forloop.counter|stringformat:"02d" }}
- {{ order.mentor.user.first_name }} {{ order.mentor.user.last_name }}
+ {{ order.mentor.first_name }} {{ order.mentor.last_name }}
-
-
diff --git a/coderdojochi/templates/dashboard/session-donations.html b/coderdojochi/templates/dashboard/session_donations.html
similarity index 98%
rename from coderdojochi/templates/dashboard/session-donations.html
rename to coderdojochi/templates/dashboard/session_donations.html
index faea7c9a..bd361b31 100644
--- a/coderdojochi/templates/dashboard/session-donations.html
+++ b/coderdojochi/templates/dashboard/session_donations.html
@@ -1,4 +1,4 @@
-{% extends "_admin-base.html" %}
+{% extends "_admin_base.html" %}
{% load i18n bootstrap3 %}
diff --git a/coderdojochi/templates/dashboard/session-stats.html b/coderdojochi/templates/dashboard/session_stats.html
similarity index 97%
rename from coderdojochi/templates/dashboard/session-stats.html
rename to coderdojochi/templates/dashboard/session_stats.html
index fdeebd78..8531e8ec 100644
--- a/coderdojochi/templates/dashboard/session-stats.html
+++ b/coderdojochi/templates/dashboard/session_stats.html
@@ -101,7 +101,7 @@ All Students
{% csrf_token %}
{{ order.student.last_name }}, {{ order.student.first_name }}
- {% if order.alternate_guardian %}{{ order.alternate_guardian }}{% else %}{{ order.guardian.user.first_name }} {{ order.guardian.user.last_name }}{% endif %}
+ {% if order.alternate_guardian %}{{ order.alternate_guardian }}{% else %}{{ order.guardian.full_name }}{% endif %}
{{ order.student | student_age:session.start_date }}
{{ order.student.gender }}
{% if order.check_in %}
diff --git a/coderdojochi/templates/guardian/session-detail.html b/coderdojochi/templates/guardian/session-detail.html
deleted file mode 100644
index bc1075c3..00000000
--- a/coderdojochi/templates/guardian/session-detail.html
+++ /dev/null
@@ -1,199 +0,0 @@
-{% extends "coderdojochi/_base.html" %}
-
-{% load static i18n humanize coderdojochi_extras %}
-
-{% block title %}{% if session.course.code %}{{ session.course.code }}: {% endif %}{{ session.course.title }} on {{ session.start_date|date }} | {{ block.super }}{% endblock %}
-{% block meta_facebook_title %}{% if session.course.code %}{{ session.course.code }}: {% endif %}{{ session.course.title }} on {{ session.start_date|date }} | {{ block.super }}{% endblock %}
-{% block meta_twitter_title %}{% if session.course.code %}{{ session.course.code }}: {% endif %}{{ session.course.title }} on {{ session.start_date|date }} | {{ block.super }}{% endblock %}
-
-{% block meta_description %}{{ session.course.description|striptags|safe }}{% endblock %}
-{% block meta_facebook_description %}{{ session.course.description|striptags|safe }}{% endblock %}
-{% block meta_twitter_description %}{{ session.course.description|striptags|safe }}{% endblock %}
-
-{% comment %} {% block meta_facebook_image %}{% endblock %} {% endcomment %}
-{% comment %} {% block meta_twitter_image %}{% endblock %} {% endcomment %}
-
-{% block body_class %}page-class-detail{% endblock %}
-
-{% block contained_content %}
-
-Class Details & Enrollment
-
-
-
- {% if session.external_enrollment_url %}
-
- Enroll now
-
- {% else %}
-
- {% if spots_remaining < 1 %}
-
- There are currently no available spots for this class. Please enroll in an upcoming class.
-
- {% else %}
-
- {% if students %}
-
- {% if spots_remaining > 0 %}
-
- Enroll Student{{ students|pluralize }}
-
- {% else %}
-
- There are currently no available spots for this class. Please join the waitlist below and/or find another upcoming class.
-
- {% endif %}
-
-
-
- {% for student in students %}
-
- {{ student.first_name }} {{ student.last_name|slice:":1" }}
-
- {% student_session_order_count student=student session=session as student_is_enrolled %}
- {% if spots_remaining > 0 or student_is_enrolled %}
- {% student_register_link student session %}
- {% else %}
-
- {% endif %}
-
-
- {% endfor %}
-
-
-
- Add another student
-
- {% else %}
-
- Enroll
-
- {% endif %}
-
- {% endif %}
-
- {% endif %}
-
-
-
-
-
{% if session.course.code %}{{ session.course.code }}: {% endif%}{{ session.course.title }}
-
{{ session.course.description|safe }}
-
- {% if session.additional_info %}
-
Additional Info: {{ session.additional_info|safe }}
- {% endif %}
-
- {% if session.online_video_link %}
-
This will be a live online class via Zoom.
-
-
Technical Requirements
-
Computer: PC (Windows XP or newer), Mac (OSX 10.7 or newer), or Chromebook with at least a 2GHz processor and 2GB of RAM (4GB of RAM is recommended).
-
-
Internet: At least 1.2Mbps download and 600Kbps upload speeds. Go to fast.com to check your speed.
-
-
Webcam: Many laptops have an integrated webcam.
-
-
Microphone and Speakers: We highly recommend headphones with a built-in microphone, however any microphone and speakers will work in a quiet room.
-
-
How To Join Online Class
-
About 10 minutes before class time, click the following link to join.
-
{{ session.online_video_link }}
-
-
Meeting ID: {{ session.online_video_meeting_id }}
- Password: {{ session.online_video_meeting_password }}
-
- {% endif %}
-
-
-
{{ session.start_date|date }}
-
- {{ session.start_date|time }} to {{ session.end_date|time }}
-
- Add to your calendar
-
-
-
-
{{ session.location.name }}
- {% if session.location.address %}
-
-
{{ session.location.address }}, {{ session.location.city }}, {{ session.location.state }} {{ session.location.zip }}
-
- {% else %}
-
-
Join Zoom Meeting
-
-
Meeting ID: {{ session.online_video_meeting_id }}
- Password: {{ session.online_video_meeting_password }}
-
- {% endif %}
-
-
-
Who
- {% if session.gender_limitation %}
-
This class is limited to {{ session.gender_limitation }}s only.
- {% endif %}
-
- {% if session.gender_limitation %}
-
{{ session.gender_limitation|title }}s between {{ session.minimum_age }} and {{ session.maximum_age }} of age. No computer skills required.
- {% else %}
-
Anyone between {{ session.minimum_age }} and {{ session.maximum_age }} of age. No computer skills required.
- {% endif %}
-
-
-
Cost
- {% if session.minimum_cost or session.maximm_cost %}
-
${{ session.minimum_cost | floatformat:-2 }} – ${{ session.maximim_cost | floatformat:-2 }}
- {% elif session.cost > 0 %}
-
${{ session.cost | floatformat:-2 }}
- {% else %}
-
Free to attend!
- {% endif %}
-
-
-
-
-{% if session.instructor %}
-
- About the instructor
-
-
-
-
-
-
-
-{% endif %}
-
-{% if active_mentors %}
-
- Meet the mentors
-
- {% for mentor in active_mentors %}
- {% if mentor.is_public == True %}
-
- {% include "weallcode/snippets/team_member.html" with is_small=True name=mentor.full_name image=mentor.avatar.thumbnail %}
-
- {% endif %}
- {% endfor %}
-
-
-{% endif %}
-
-{% endblock %}
diff --git a/coderdojochi/templates/guardian/session_detail.html b/coderdojochi/templates/guardian/session_detail.html
new file mode 100644
index 00000000..6d33be2e
--- /dev/null
+++ b/coderdojochi/templates/guardian/session_detail.html
@@ -0,0 +1,200 @@
+{% extends "coderdojochi/_base.html" %}
+
+{% load static i18n humanize coderdojochi_extras %}
+
+{% block title %}{% if object.course.code %}{{ object.course.code }}: {% endif %}{{ object.course.title }} on {{ object.start_date|date }} | {{ block.super }}{% endblock %}
+{% block meta_facebook_title %}{% if object.course.code %}{{ object.course.code }}: {% endif %}{{ object.course.title }} on {{ object.start_date|date }} | {{ block.super }}{% endblock %}
+{% block meta_twitter_title %}{% if object.course.code %}{{ object.course.code }}: {% endif %}{{ object.course.title }} on {{ object.start_date|date }} | {{ block.super }}{% endblock %}
+
+{% block meta_description %}{{ object.course.description|striptags|safe }}{% endblock %}
+{% block meta_facebook_description %}{{ object.course.description|striptags|safe }}{% endblock %}
+{% block meta_twitter_description %}{{ object.course.description|striptags|safe }}{% endblock %}
+
+
+
+{% block body_class %}page-class-detail{% endblock %}
+
+{% block contained_content %}
+
+Class Details & Enrollment
+
+
+
+ {% if object.external_enrollment_url %}
+
+ Enroll now
+
+ {% else %}
+
+ {% if spots_remaining < 1 %}
+
+ There are currently no available spots for this class. Please enroll in an upcoming class.
+
+ {% else %}
+
+ {% if students %}
+
+ {% if spots_remaining > 0 %}
+
+ Enroll Student{{ students|pluralize }}
+
+ {% else %}
+
+ There are currently no available spots for this class. Please join the waitlist below and/or find another upcoming class.
+
+ {% endif %}
+
+
+
+ {% for student in students %}
+
+ {{ student.first_name }} {{ student.last_name|slice:":1" }}
+
+ {% student_session_order_count student=student session=session as student_is_enrolled %}
+ {% if spots_remaining > 0 or student_is_enrolled %}
+ {% student_register_link student session %}
+ {% else %}
+
+ {% endif %}
+
+
+ {% endfor %}
+
+
+
+ Add another student
+
+ {% else %}
+
+ Enroll
+
+ {% endif %}
+
+ {% endif %}
+
+ {% endif %}
+
+
+
+
+
{% if object.course.code %}{{ object.course.code }}: {% endif%}{{ object.course.title }}
+
{{ object.course.description|safe }}
+
+ {% if object.additional_info %}
+
Additional Info: {{ object.additional_info|safe }}
+ {% endif %}
+
+ {% if object.online_video_link %}
+
This will be a live online class via Zoom.
+
+
Technical Requirements
+
Computer: PC (Windows XP or newer), Mac (OSX 10.7 or newer), or Chromebook with at least a 2GHz processor and 2GB of RAM (4GB of RAM is recommended).
+
+
Internet: At least 1.2Mbps download and 600Kbps upload speeds. Go to fast.com to check your speed.
+
+
Webcam: Many laptops have an integrated webcam.
+
+
Microphone and Speakers: We highly recommend headphones with a built-in microphone, however any microphone and speakers will work in a quiet room.
+
+
How To Join Online Class
+
About 10 minutes before class time, click the following link to join.
+
{{ object.online_video_link }}
+
+
Meeting ID: {{ object.online_video_meeting_id }}
+ Password: {{ object.online_video_meeting_password }}
+
+ {% endif %}
+
+
+
+
{{ object.start_date|date }}
+
{{ object.start_date|time }} to {{ object.end_date|time }}
+
Add to your calendar
+
+
{{ object.location.name }}
+
+ {% if object.location.address %}
+
+
{{ object.location.address }}, {{ object.location.city }}, {{ object.location.state }} {{ object.location.zip }}
+
+ {% else %}
+
+
Join Zoom Meeting
+
+
+ Meeting ID: {{ object.online_video_meeting_id }}
+ Password: {{ object.online_video_meeting_password }}
+
+
+ {% endif %}
+
+
+
Who
+ {% if object.gender_limitation %}
+
This class is limited to {{ object.gender_limitation }}s only.
+ {% endif %}
+
+ {% if object.gender_limitation %}
+
{{ object.gender_limitation|title }}s between {{ object.minimum_age }} and {{ object.maximum_age }} of age. No computer skills required.
+ {% else %}
+
Anyone between {{ object.minimum_age }} and {{ object.maximum_age }} of age. No computer skills required.
+ {% endif %}
+
+
+
Cost
+ {% if object.minimum_cost or object.maximm_cost %}
+
${{ object.minimum_cost | floatformat:-2 }} – ${{ object.maximim_cost | floatformat:-2 }}
+ {% elif object.cost > 0 %}
+
${{ object.cost | floatformat:-2 }}
+ {% else %}
+
Free to attend!
+ {% endif %}
+
+
+
+
+{% if object.instructor %}
+
+ About the instructor
+
+
+{% endif %}
+
+{% if active_mentors %}
+
+ Meet the mentors
+
+ {% for mentor in active_mentors %}
+ {% if mentor.is_public == True %}
+
+ {% include "weallcode/snippets/team_member.html" with is_small=True name=mentor.full_name image=mentor.get_avatar.thumbnail %}
+
+ {% endif %}
+ {% endfor %}
+
+
+{% endif %}
+
+{% endblock %}
diff --git a/coderdojochi/templates/guardian/session-sign-up.html b/coderdojochi/templates/guardian/session_sign_up.html
similarity index 57%
rename from coderdojochi/templates/guardian/session-sign-up.html
rename to coderdojochi/templates/guardian/session_sign_up.html
index 108cf888..b0718727 100644
--- a/coderdojochi/templates/guardian/session-sign-up.html
+++ b/coderdojochi/templates/guardian/session_sign_up.html
@@ -10,7 +10,7 @@
{% if user_signed_up %}
-
{{ student.first_name }} {{ student.last_name }} can no longer make it to the "{{ session.course.title }}" class on {{ session.start_date|date }} from {{ session.start_date|time }} to {{ session.end_date|time }} .
+
{{ student.full_name }} can no longer make it to the "{{ session.course.title }}" class on {{ session.start_date|date }} from {{ session.start_date|time }} to {{ session.end_date|time }} .
{% endfor %}
diff --git a/coderdojochi/templatetags/coderdojochi_extras.py b/coderdojochi/templatetags/coderdojochi_extras.py
index c37c15be..ae96e776 100644
--- a/coderdojochi/templatetags/coderdojochi_extras.py
+++ b/coderdojochi/templatetags/coderdojochi_extras.py
@@ -16,105 +16,64 @@ def subtract(value, arg):
@register.simple_tag(takes_context=False)
def student_session_order_count(student, session):
- orders_count = Order.objects.filter(
- student=student,
- session=session
- ).count()
+ orders_count = Order.objects.filter(student=student, session=session).count()
return orders_count
@register.simple_tag(takes_context=True)
def student_register_link(context, student, session):
- orders = Order.objects.filter(
- student=student,
- session=session,
- is_active=True
+ orders = Order.objects.filter(student=student, session=session, is_active=True)
+
+ url = reverse(
+ "session-sign-up",
+ kwargs={
+ "pk": session.id,
+ "student_id": student.id,
+ },
)
- url = reverse('session-sign-up', kwargs={'pk': session.id, 'student_id': student.id, })
-
- button_tag = 'a'
- button_modifier = ''
- button_additional_attributes = ''
- button_msg = 'Enroll'
- button_href = f'href={url}'
+ button_tag = "a"
+ button_modifier = ""
+ button_additional_attributes = ""
+ button_msg = "Enroll"
+ button_href = f"href={url}"
if orders.count():
button_modifier = "tertiary"
button_msg = "Can't make it"
- elif (
- not student.is_within_age_range(
- session.minimum_age,
- session.maximum_age,
- session.start_date
- ) or
- not student.is_within_gender_limitation(
- session.gender_limitation
- )
- ):
- button_modifier = 'btn-default'
- button_additional_attributes = 'disabled'
- button_tag = 'span'
-
- if (
- not student.is_within_age_range(
- session.minimum_age,
- session.maximum_age,
- session.start_date
- ) and
- not student.is_within_gender_limitation(
- session.gender_limitation
- )
- ):
+ elif not student.is_within_age_range(
+ session.minimum_age, session.maximum_age, session.start_date
+ ) or not student.is_within_gender_limitation(session.gender_limitation):
+ button_modifier = "btn-default"
+ button_additional_attributes = "disabled"
+ button_tag = "span"
+
+ if not student.is_within_age_range(
+ session.minimum_age, session.maximum_age, session.start_date
+ ) and not student.is_within_gender_limitation(session.gender_limitation):
title = "Limited event."
- message = (
- f"Sorry, this class is limited to {session.gender_limitation}s between {session.minimum_age} "
- f"and {session.maximum_age} this time around."
- )
-
- button_href = (
- 'data-trigger="hover" data-placement="top" data-toggle="popover" title="" '
- f'data-content="{message}" data-original-title="{title}"'
- )
-
- elif (
- not student.is_within_age_range(
- session.minimum_age,
- session.maximum_age,
- session.start_date
- )
- ):
+ message = f"Sorry, this class is limited to {session.gender_limitation}s between {session.minimum_age} and {session.maximum_age} this time around."
+
+ button_href = f'data-trigger="hover" data-placement="top" data-toggle="popover" title="" data-content="{message}" data-original-title="{title}"'
+
+ elif not student.is_within_age_range(session.minimum_age, session.maximum_age, session.start_date):
title = "Age-limited event."
- message = (
- f"Sorry, this class is limited to student between ages {session.minimum_age} and "
- f"{session.maximum_age} this time around."
- )
- button_href = (
- 'data-trigger="hover" data-placement="top" data-toggle="popover" title="" '
- f'data-content="{message}" data-original-title="{title}"'
- )
-
- elif (
- not student.is_within_gender_limitation(
- session.gender_limitation
- )
- ):
- title = "{gender}-only event.".format(
- gender='Girls' if session.gender_limitation == 'female' else 'Boys'
- )
+ message = f"Sorry, this class is limited to student between ages {session.minimum_age} and {session.maximum_age} this time around."
+
+ button_href = f'data-trigger="hover" data-placement="top" data-toggle="popover" title="" data-content="{message}" data-original-title="{title}"'
+
+ elif not student.is_within_gender_limitation(session.gender_limitation):
+ if session.gender_limitation == "female":
+ title = "Girls-only event."
+ else:
+ title = "Boys-only event."
+
message = f"Sorry, this class is limited to {session.gender_limitation}s this time around."
- button_href = (
- 'data-trigger="hover" data-placement="top" data-toggle="popover" title="" '
- f'data-content="{message}" data-original-title="{title}" '
- )
-
- form = (
- f"<{button_tag} {button_href} class='button small {button_modifier}' {button_additional_attributes}>"
- f"{button_msg}"
- f"{button_tag}>"
- )
+ button_href = f'data-trigger="hover" data-placement="top" data-toggle="popover" title="" data-content="{message}" data-original-title="{title}" '
+
+ form = f"<{button_tag} {button_href} class='button small {button_modifier}' {button_additional_attributes}>{button_msg}{button_tag}>"
return Template(form).render(context)
@@ -125,10 +84,13 @@ def student_age(student, date):
@register.simple_tag(takes_context=True)
-def menu_is_active(context, pattern_or_urlname, css_class='active'):
+def menu_is_active(context, pattern_or_urlname, css_class="active"):
try:
- pattern = '^' + reverse(pattern_or_urlname)
+ pattern = "^" + reverse(pattern_or_urlname)
except NoReverseMatch:
pattern = pattern_or_urlname
- return css_class if re.search(pattern, context['request'].path) else ''
+ if re.search(pattern, context["request"].path):
+ return css_class
+ else:
+ return ""
diff --git a/coderdojochi/tests/test_mentor_updates.py b/coderdojochi/tests/test_mentor_updates.py
index 4804d215..8294a010 100644
--- a/coderdojochi/tests/test_mentor_updates.py
+++ b/coderdojochi/tests/test_mentor_updates.py
@@ -1,5 +1,3 @@
-from unittest import TestCase
-
from django.test import TransactionTestCase
import mock
@@ -8,7 +6,7 @@
class TestMentorAvatarUpdates(TransactionTestCase):
- @mock.patch('coderdojochi.signals_handlers.EmailMultiAlternatives')
+ @mock.patch("coderdojochi.signals_handlers.EmailMultiAlternatives")
def test_new_mentor_no_avatar(self, mock_email):
mentor = Mentor.objects.create()
self.fail()
diff --git a/coderdojochi/tests/test_password_session.py b/coderdojochi/tests/test_password_session.py
index a1b2c9d3..aeb8dc29 100644
--- a/coderdojochi/tests/test_password_session.py
+++ b/coderdojochi/tests/test_password_session.py
@@ -1,92 +1,62 @@
-# import mock
-# import sys
-
from django.contrib.auth import get_user_model
from django.http import HttpResponseRedirect
-# from django.test import RequestFactory
from django.test import Client, TestCase
from django.urls import reverse
from coderdojochi.factories import PartnerPasswordAccessFactory, SessionFactory
from coderdojochi.models import PartnerPasswordAccess
-# from coderdojochi.factories import CDCUserFactory
-# from coderdojochi.views import session_detail
-
-
User = get_user_model()
class TestPartnerSessionPassword(TestCase):
def setUp(self):
- self.partner_session = SessionFactory.create(password='124')
+ self.partner_session = SessionFactory.create(password="124")
self.url_kwargs = {
- 'year': self.partner_session.start_date.year,
- 'month': self.partner_session.start_date.month,
- 'day': self.partner_session.start_date.day,
- 'slug': self.partner_session.course.slug,
- 'session_id': self.partner_session.id
+ "year": self.partner_session.start_date.year,
+ "month": self.partner_session.start_date.month,
+ "day": self.partner_session.start_date.day,
+ "slug": self.partner_session.course.slug,
+ "session_id": self.partner_session.id,
}
self.client = Client()
- self.url = reverse('session_password', kwargs=self.url_kwargs)
+ self.url = reverse("session_password", kwargs=self.url_kwargs)
def test_session_password_invalid_password(self):
- response = self.client.post(self.url, data={'password': 'abc'})
- self.assertContains(response, 'Invalid password.')
+ response = self.client.post(self.url, data={"password": "abc"})
+ self.assertContains(response, "Invalid password.")
def test_session_password_no_password(self):
- response = self.client.post(self.url, data={'password': ''})
- self.assertContains(response, 'Must enter a password.')
+ response = self.client.post(self.url, data={"password": ""})
+ self.assertContains(response, "Must enter a password.")
def test_session_password_valid_password_unauthed(self):
- response = self.client.post(
- self.url,
- data={
- 'password': self.partner_session.password
- }
- )
+ response = self.client.post(self.url, data={"password": self.partner_session.password})
self.assertIsInstance(response, HttpResponseRedirect)
- detail_url = reverse('session_detail', kwargs=self.url_kwargs)
+ detail_url = reverse("session_detail", kwargs=self.url_kwargs)
self.assertEqual(response.url, detail_url)
password_access_count = PartnerPasswordAccess.objects.count()
self.assertEqual(password_access_count, 0)
- authed_sessions = self.client.session['authed_partner_sessions']
+ authed_sessions = self.client.session["authed_partner_sessions"]
self.assertFalse(str(self.partner_session.id) in authed_sessions)
def test_session_password_valid_password_authed(self):
- user = User.objects.create_user(
- 'user',
- email='email@email.com',
- password='pass123'
- )
- self.assertTrue(
- self.client.login(
- email='email@email.com',
- password='pass123'
- )
- )
-
- response = self.client.post(
- self.url,
- data={
- 'password': self.partner_session.password
- }
- )
+ user = User.objects.create_user("user", email="email@email.com", password="pass123")
+ self.assertTrue(self.client.login(email="email@email.com", password="pass123"))
+
+ response = self.client.post(self.url, data={"password": self.partner_session.password})
self.assertIsInstance(response, HttpResponseRedirect)
- detail_url = reverse('session_detail', kwargs=self.url_kwargs)
+ detail_url = reverse("session_detail", kwargs=self.url_kwargs)
self.assertEqual(response.url, detail_url)
- partner_password_access = PartnerPasswordAccess.objects.get(
- session=self.partner_session,
- user=user
- )
+ partner_password_access = PartnerPasswordAccess.objects.get(session=self.partner_session, user=user)
self.assertIsNotNone(partner_password_access)
- authed_sessions = self.client.session['authed_partner_sessions']
+ authed_sessions = self.client.session["authed_partner_sessions"]
self.assertTrue(str(self.partner_session.id) in authed_sessions)
@@ -94,61 +64,40 @@ class TestSessionDetail(TestCase):
def setUp(self):
super(TestSessionDetail, self).setUp()
self.client = Client()
- self.partner_session = SessionFactory.create(password='124')
+ self.partner_session = SessionFactory.create(password="124")
self.url_kwargs = {
- 'year': self.partner_session.start_date.year,
- 'month': self.partner_session.start_date.month,
- 'day': self.partner_session.start_date.day,
- 'slug': self.partner_session.course.slug,
- 'session_id': self.partner_session.id
+ "year": self.partner_session.start_date.year,
+ "month": self.partner_session.start_date.month,
+ "day": self.partner_session.start_date.day,
+ "slug": self.partner_session.course.slug,
+ "session_id": self.partner_session.id,
}
- self.url = reverse('session_detail', kwargs=self.url_kwargs)
+ self.url = reverse("session_detail", kwargs=self.url_kwargs)
def test_redirect_password_unauthed(self):
response = self.client.get(self.url)
self.assertIsInstance(response, HttpResponseRedirect)
- detail_url = reverse('session_password', kwargs=self.url_kwargs)
+ detail_url = reverse("session_password", kwargs=self.url_kwargs)
self.assertEqual(response.url, detail_url)
def test_redirect_password_authed(self):
- User.objects.create_user(
- 'user',
- email='email@email.com',
- password='pass123'
- )
- self.assertTrue(
- self.client.login(
- email='email@email.com',
- password='pass123'
- )
- )
+ User.objects.create_user("user", email="email@email.com", password="pass123")
+ self.assertTrue(self.client.login(email="email@email.com", password="pass123"))
response = self.client.get(self.url)
self.assertIsInstance(response, HttpResponseRedirect)
- detail_url = reverse('session_password', kwargs=self.url_kwargs)
+ detail_url = reverse("session_password", kwargs=self.url_kwargs)
self.assertEqual(response.url, detail_url)
def test_redirect_password_partner_password_access(self):
- user = User.objects.create_user(
- 'user',
- email='email@email.com',
- password='pass123'
- )
- self.assertTrue(
- self.client.login(
- email='email@email.com',
- password='pass123'
- )
- )
-
- PartnerPasswordAccessFactory.create(
- user=user,
- session=self.partner_session
- )
+ user = User.objects.create_user("user", email="email@email.com", password="pass123")
+ self.assertTrue(self.client.login(email="email@email.com", password="pass123"))
+
+ PartnerPasswordAccessFactory.create(user=user, session=self.partner_session)
response = self.client.get(self.url)
- detail_url = reverse('session_password', kwargs=self.url_kwargs)
+ detail_url = reverse("session_password", kwargs=self.url_kwargs)
# don't care what its doing as long as its not
# redirecting to the password url.
diff --git a/coderdojochi/urls.py b/coderdojochi/urls.py
index 4ce558c1..924c4cb8 100644
--- a/coderdojochi/urls.py
+++ b/coderdojochi/urls.py
@@ -1,32 +1,26 @@
from django.conf import settings
-from django.conf.urls import include, url
+from django.conf.urls import include
from django.conf.urls.static import static
from django.contrib import admin
-from django.contrib.auth import views as django_views
from django.http import HttpResponse
from django.urls import path
from django.views import defaults
from django.views.generic import RedirectView
-from loginas import views as loginas_views
-
from . import old_views
-from .views.meetings import (
+from .views import ( # SessionDetailView,
MeetingCalendarView,
MeetingDetailView,
MeetingsView,
- meeting_announce,
- meeting_sign_up,
-)
-from .views.mentor import MentorDetailView, MentorListView
-from .views.profile import DojoMentorView
-from .views.sessions import (
PasswordSessionView,
SessionCalendarView,
SessionDetailView,
- SessionSignUpView
+ SessionSignUpView,
+ WelcomeView,
+ meeting_announce,
+ meeting_sign_up,
)
-from .views.welcome import WelcomeView
+from .views.public import MentorDetailView, MentorListView
admin.autodiscover()
@@ -35,151 +29,165 @@
# General Pages
urlpatterns += [
- path('', include('weallcode.urls')),
+ path("", include("weallcode.urls")),
]
# Accounts
urlpatterns += [
- path('account/', include('accounts.urls')),
+ path("account/", include("accounts.urls")),
]
# Old General
urlpatterns += [
-
- path('old/', include([
-
- # Meetings
- path('meetings/', include([
- # Meetings
- # /meetings/
- path('', MeetingsView.as_view(), name='meetings'),
-
- # Individual Meeting
- # /meeting/ID/
- path('/', MeetingDetailView.as_view(), name='meeting-detail'),
-
- # /meeting/ID/announce/
- path('/announce/', meeting_announce, name='meeting-announce'),
-
- # Meeting sign up
- # /meeting/ID/sign-up/
- path('/register/', meeting_sign_up, name='meeting-register'),
-
- # Meeting Calendar
- # /meeting/ID/calendar/
- path('/calendar/', MeetingCalendarView.as_view(), name='meeting-calendar'),
- ])),
-
- ])),
-
+ path(
+ "old/",
+ include(
+ [
+ # Meetings
+ path(
+ "meetings/",
+ include(
+ [
+ # Meetings
+ # /meetings/
+ path("", MeetingsView.as_view(), name="meetings"),
+ # Individual Meeting
+ # /meeting/ID/
+ path("/", MeetingDetailView.as_view(), name="meeting-detail"),
+ # /meeting/ID/announce/
+ path("/announce/", meeting_announce, name="meeting-announce"),
+ # Meeting sign up
+ # /meeting/ID/sign-up/
+ path("/register/", meeting_sign_up, name="meeting-register"),
+ # Meeting Calendar
+ # /meeting/ID/calendar/
+ path("/calendar/", MeetingCalendarView.as_view(), name="meeting-calendar"),
+ ]
+ ),
+ ),
+ ]
+ ),
+ ),
]
# Login As
urlpatterns += [
- path('dj-admin/', include('loginas.urls')),
+ path("dj-admin/", include("loginas.urls")),
]
# Django Admin
urlpatterns += [
# /dj-admin/
- path('dj-admin/', admin.site.urls),
+ path("dj-admin/", admin.site.urls),
]
# Admin
urlpatterns += [
- path('admin/', include([
- # Admin
- # /admin/
- path('', old_views.cdc_admin, name='cdc-admin'),
-
- path('classes/', include([
- # /admin/classes/ID/stats/
- path('/stats/', old_views.session_stats, name='stats'),
-
- # /admin/classes/ID/check-in/
- path('/check-in/', old_views.session_check_in, name='student-check-in'),
-
- # /admin/classes/ID/check-in-mentors/
- path('/check-in-mentors/', old_views.session_check_in_mentors, name='mentor-check-in'),
-
- # /admin/classes/ID/donations/
- path('/donations/', old_views.session_donations, name='donations'),
- ])),
-
- path('meetings/', include([
- # /admin/meeting/ID/check-in/
- path('/check-in/', old_views.meeting_check_in, name='meeting-check-in'),
- ])),
-
- # Admin Check System
- # /admin/checksystem/
- path('checksystem/', old_views.check_system, name='check-system'),
- ]))
+ path(
+ "admin/",
+ include(
+ [
+ # Admin
+ # /admin/
+ path("", old_views.cdc_admin, name="cdc-admin"),
+ path(
+ "classes/",
+ include(
+ [
+ # /admin/classes/ID/stats/
+ path("/stats/", old_views.session_stats, name="stats"),
+ # /admin/classes/ID/check-in/
+ path("/check-in/", old_views.session_check_in, name="student-check-in"),
+ # /admin/classes/ID/check-in-mentors/
+ path(
+ "/check-in-mentors/", old_views.session_check_in_mentors, name="mentor-check-in"
+ ),
+ # /admin/classes/ID/donations/
+ path("/donations/", old_views.session_donations, name="donations"),
+ ]
+ ),
+ ),
+ path(
+ "meetings/",
+ include(
+ [
+ # /admin/meeting/ID/check-in/
+ path("/check-in/", old_views.meeting_check_in, name="meeting-check-in"),
+ ]
+ ),
+ ),
+ # Admin Check System
+ # /admin/checksystem/
+ path("checksystem/", old_views.check_system, name="check-system"),
+ ]
+ ),
+ )
]
# Sessions
urlpatterns += [
-
- path('classes/', include([
- # Classes
- # /classes/
- path('', RedirectView.as_view(pattern_name='weallcode-programs'), name='sessions'),
-
- # Individual Class
- # /classes/ID/
- path('/', SessionDetailView.as_view(), name='session-detail'),
-
- # Password
- # /classes/ID/password/
- path('/password/', PasswordSessionView.as_view(), name='session-password'),
-
- # Announce
- # /classes/ID/announce/mentors/
- path('/announce/mentors/', old_views.session_announce_mentors, name='session-announce-mentors'),
-
- # /classes/ID/announce/guardians/
- path('/announce/guardians/', old_views.session_announce_guardians, name='session-announce-guardians'),
-
- # Calendar
- # /classes/ID/calendar/
- path('/calendar/', SessionCalendarView.as_view(), name='session-calendar'),
-
- # Sign up
- # /classes/ID/sign-up/
- path('/sign-up/', SessionSignUpView.as_view(), name='session-sign-up'),
-
- # /classes/ID/sign-up/STUDENT-ID/
- path('/sign-up//', SessionSignUpView.as_view(), name='session-sign-up'),
- ])),
-
+ path(
+ "classes/",
+ include(
+ [
+ # Classes
+ # /classes/
+ path("", RedirectView.as_view(pattern_name="weallcode-programs"), name="sessions"),
+ # Individual Class
+ # /classes/ID/
+ path("/", SessionDetailView.as_view(), name="session-detail"),
+ # Password
+ # /classes/ID/password/
+ path("/password/", PasswordSessionView.as_view(), name="session-password"),
+ # Announce
+ # /classes/ID/announce/mentors/
+ path("/announce/mentors/", old_views.session_announce_mentors, name="session-announce-mentors"),
+ # /classes/ID/announce/guardians/
+ path(
+ "/announce/guardians/",
+ old_views.session_announce_guardians,
+ name="session-announce-guardians",
+ ),
+ # Calendar
+ # /classes/ID/calendar/
+ path("/calendar/", SessionCalendarView.as_view(), name="session-calendar"),
+ # Sign up
+ # /classes/ID/sign-up/
+ path("/sign-up/", SessionSignUpView.as_view(), name="session-sign-up"),
+ # /classes/ID/sign-up/STUDENT-ID/
+ path("/sign-up//", SessionSignUpView.as_view(), name="session-sign-up"),
+ ]
+ ),
+ ),
]
# Mentors
# TODO: Uncomment `app_name` after we move mentors to it's own app.
# app_name = 'mentors'
urlpatterns += [
- path('mentors/', include([
- # Mentors
- # /
- path('', MentorListView.as_view(), name='mentors'),
-
- # /ID/
- path('/', MentorDetailView.as_view(), name='mentor-detail'),
-
- # /ID/reject-avatar/
- path('/reject-avatar/', old_views.mentor_reject_avatar, name='mentor-reject-avatar'),
-
- # /ID/approve-avatar/
- path('/approve-avatar/', old_views.mentor_approve_avatar, name='mentor-approve-avatar'),
- ])),
-
+ path(
+ "mentors/",
+ include(
+ [
+ # Mentors
+ # /
+ path("", MentorListView.as_view(), name="mentors"),
+ # /ID/
+ path("/", MentorDetailView.as_view(), name="mentor-detail"),
+ # /ID/reject-avatar/
+ path("/reject-avatar/", old_views.mentor_reject_avatar, name="mentor-reject-avatar"),
+ # /ID/approve-avatar/
+ path("/approve-avatar/", old_views.mentor_approve_avatar, name="mentor-approve-avatar"),
+ ]
+ ),
+ ),
]
# Students
urlpatterns += [
# Student
# /student/ID/
- path('students//', old_views.student_detail, name='student-detail'),
+ path("students//", old_views.student_detail, name="student-detail"),
]
# Dojo
@@ -187,33 +195,31 @@
# Dojo / Account
# /dojo/
# path('dojo/', old_views.dojo, name='dojo'),
-
# Welcome
# /welcome/
- path('welcome/', WelcomeView.as_view(), name='welcome'),
+ path("welcome/", WelcomeView.as_view(), name="welcome"),
]
# Meetings
-urlpatterns += [
-
-]
+urlpatterns += []
# robots.txt
urlpatterns += [
- path('robots.txt', lambda r: HttpResponse('User-agent: *\nDisallow:\nSitemap: ' +
- settings.SITE_URL + '/sitemap.xml', content_type='text/plain'))
+ path(
+ "robots.txt",
+ lambda r: HttpResponse(
+ "User-agent: *\nDisallow:\nSitemap: " + settings.SITE_URL + "/sitemap.xml", content_type="text/plain"
+ ),
+ )
]
# Anymail
urlpatterns += [
- path('anymail/', include('anymail.urls')),
+ path("anymail/", include("anymail.urls")),
]
# Media
-urlpatterns += static(
- settings.MEDIA_URL,
- document_root=settings.MEDIA_ROOT
-)
+urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
@@ -224,23 +230,24 @@
# these url in browser to see how these error pages look like.
urlpatterns += [
path(
- '400/',
+ "400/",
defaults.bad_request,
- kwargs={'exception': Exception('Bad Request!')},
+ kwargs={"exception": Exception("Bad Request!")},
),
path(
- '403/',
+ "403/",
defaults.permission_denied,
- kwargs={'exception': Exception('Permission Denied')},
+ kwargs={"exception": Exception("Permission Denied")},
),
path(
- '404/',
+ "404/",
defaults.page_not_found,
- kwargs={'exception': Exception('Page not Found')},
+ kwargs={"exception": Exception("Page not Found")},
),
- path('500/', defaults.server_error),
+ path("500/", defaults.server_error),
]
- if 'debug_toolbar' in settings.INSTALLED_APPS:
+ if "debug_toolbar" in settings.INSTALLED_APPS:
import debug_toolbar
- urlpatterns = [path('__debug__/', include(debug_toolbar.urls))] + urlpatterns
+
+ urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns
diff --git a/coderdojochi/util.py b/coderdojochi/util.py
index 0a8ff48c..0da1087e 100644
--- a/coderdojochi/util.py
+++ b/coderdojochi/util.py
@@ -2,11 +2,9 @@
from django.conf import settings
from django.contrib.auth import get_user_model
-from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils import timezone
-from anymail.exceptions import AnymailAPIError
from anymail.message import AnymailMessage
logger = logging.getLogger(__name__)
@@ -40,12 +38,12 @@ def email(
if bcc not in [False, None] and not isinstance(bcc, list):
raise TypeError("recipients must be a list")
- merge_global_data['subject'] = subject
- merge_global_data['current_year'] = timezone.now().year
- merge_global_data['company_name'] = settings.SITE_NAME
- merge_global_data['site_url'] = settings.SITE_URL
- merge_global_data['preheader'] = preheader
- merge_global_data['unsub_group_id'] = unsub_group_id
+ merge_global_data["subject"] = subject
+ merge_global_data["current_year"] = timezone.now().year
+ merge_global_data["company_name"] = settings.SITE_NAME
+ merge_global_data["site_url"] = settings.SITE_URL
+ merge_global_data["preheader"] = preheader
+ merge_global_data["unsub_group_id"] = unsub_group_id
body = render_to_string(f"{template_name}.html", merge_global_data)
@@ -55,16 +53,19 @@ def email(
final_merge_global_data = {}
for key, val in merge_global_data.items():
if merge_field_format.format(key) in body:
- final_merge_global_data[key] = "" if val is None else str(val)
-
- esp_extra={
- 'merge_field_format': merge_field_format,
- 'categories': [template_name],
+ if val is None:
+ final_merge_global_data[key] = ""
+ else:
+ final_merge_global_data[key] = str(val)
+
+ esp_extra = {
+ "merge_field_format": merge_field_format,
+ "categories": [template_name],
}
if unsub_group_id:
- esp_extra['asm'] = {
- 'group_id': unsub_group_id,
+ esp_extra["asm"] = {
+ "group_id": unsub_group_id,
}
for recipients_batch in batches(recipients, batch_size):
@@ -96,10 +97,8 @@ def email(
for recipient in msg.anymail_status.recipients.keys():
send_attempt = msg.anymail_status.recipients[recipient]
- if send_attempt.status not in ['queued', 'sent']:
- logger.error(
- f"user: {recipient}, {timezone.now()}"
- )
+ if send_attempt.status not in ["queued", "sent"]:
+ logger.error(f"user: {recipient}, {timezone.now()}")
user = User.objects.get(email=recipient)
user.is_active = False
@@ -109,4 +108,4 @@ def email(
def batches(l, n):
for i in range(0, len(l), n):
- yield l[i:i + n]
+ yield l[i : i + n]
diff --git a/coderdojochi/views/__init__.py b/coderdojochi/views/__init__.py
index e69de29b..d860a77e 100644
--- a/coderdojochi/views/__init__.py
+++ b/coderdojochi/views/__init__.py
@@ -0,0 +1,5 @@
+from .calendar import *
+from .meetings import *
+from .profile import *
+from .sessions import *
+from .welcome import *
diff --git a/coderdojochi/views/calendar.py b/coderdojochi/views/calendar.py
index d8d6ad64..5a4f758c 100644
--- a/coderdojochi/views/calendar.py
+++ b/coderdojochi/views/calendar.py
@@ -28,47 +28,38 @@ def get_location(self, request, event_obj):
raise NotImplementedError
def get(self, request, *args, **kwargs):
- event_obj = get_object_or_404(
- self.event_class,
- id=kwargs[self.event_kwarg]
- )
+ event_obj = get_object_or_404(self.event_class, id=kwargs[self.event_kwarg])
cal = Calendar()
- cal['prodid'] = '-//We All Code//weallcode.org//'
- cal['version'] = '2.0'
- cal['calscale'] = 'GREGORIAN'
+ cal["prodid"] = "-//We All Code//weallcode.org//"
+ cal["version"] = "2.0"
+ cal["calscale"] = "GREGORIAN"
event = Event()
- event['uid'] = f"{self.event_type.upper()}{event_obj.id:04}@weallcode.org"
- event['summary'] = self.get_summary(request, event_obj)
- event['dtstart'] = self.get_dtstart(request, event_obj)
- event['dtend'] = self.get_dtend(request, event_obj)
- event['dtstamp'] = event['dtstart'][:-1]
- event['location'] = vText(self.get_location(request, event_obj))
- event['url'] = f"{settings.SITE_URL}{event_obj.get_absolute_url()}"
- event['description'] = self.get_description(event_obj)
+ event["uid"] = f"{self.event_type.upper()}{event_obj.id:04}@weallcode.org"
+ event["summary"] = self.get_summary(request, event_obj)
+ event["dtstart"] = self.get_dtstart(request, event_obj)
+ event["dtend"] = self.get_dtend(request, event_obj)
+ event["dtstamp"] = event["dtstart"][:-1]
+ event["location"] = vText(self.get_location(request, event_obj))
+ event["url"] = f"{settings.SITE_URL}{event_obj.get_absolute_url()}"
+ event["description"] = self.get_description(event_obj)
# A value of 5 is the normal or "MEDIUM" priority.
# see: https://tools.ietf.org/html/rfc5545#section-3.8.1.9
- event['priority'] = 5
+ event["priority"] = 5
cal.add_component(event)
event_slug = "weallcode-{event_type}_{date}".format(
event_type=self.event_type.lower(),
- date=arrow.get(
- event_obj.start_date
- ).to('local').format('MM-DD-YYYY_HH-mma')
+ date=arrow.get(event_obj.start_date).to("local").format("MM-DD-YYYY_HH-mma"),
)
# Return the ICS formatted calendar
- response = HttpResponse(
- cal.to_ical(),
- content_type='text/calendar',
- charset='utf-8'
- )
+ response = HttpResponse(cal.to_ical(), content_type="text/calendar", charset="utf-8")
- response['Content-Disposition'] = f"attachment;filename={event_slug}.ics"
+ response["Content-Disposition"] = f"attachment;filename={event_slug}.ics"
return response
diff --git a/coderdojochi/views/guardian/__init__.py b/coderdojochi/views/guardian/__init__.py
new file mode 100644
index 00000000..494eed44
--- /dev/null
+++ b/coderdojochi/views/guardian/__init__.py
@@ -0,0 +1 @@
+from .sessions import *
diff --git a/coderdojochi/views/guardian/sessions.py b/coderdojochi/views/guardian/sessions.py
new file mode 100644
index 00000000..24917a23
--- /dev/null
+++ b/coderdojochi/views/guardian/sessions.py
@@ -0,0 +1,18 @@
+from django.shortcuts import get_object_or_404
+from django.views.generic import DetailView
+
+from ...models import Guardian, Session
+
+
+class SessionDetailView(DetailView):
+ model = Session
+ template_name = "guardian/session_detail.html"
+
+ def get_context_data(self, **kwargs):
+ guardian = get_object_or_404(Guardian, user=self.request.user)
+
+ context = super().get_context_data(**kwargs)
+ context["students"] = guardian.get_students()
+ context["spots_remaining"] = self.object.capacity - self.object.get_active_student_count()
+
+ return context
diff --git a/coderdojochi/views/meetings.py b/coderdojochi/views/meetings.py
index e9ce8f4b..79e582c4 100644
--- a/coderdojochi/views/meetings.py
+++ b/coderdojochi/views/meetings.py
@@ -1,55 +1,17 @@
-import calendar
import logging
-import operator
-from collections import Counter
-from datetime import date, timedelta
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model
-from django.contrib.auth.decorators import login_required
-from django.core.exceptions import ObjectDoesNotExist
-from django.core.mail import EmailMultiAlternatives, get_connection
-from django.db.models import Case, Count, IntegerField, When
-from django.http import Http404, HttpResponse
+from django.http import Http404
from django.shortcuts import get_object_or_404, redirect, render
-from django.urls import reverse
from django.utils import timezone
-from django.utils.decorators import method_decorator
-from django.utils.functional import cached_property
from django.utils.html import strip_tags
-from django.views.decorators.cache import never_cache
-from django.views.decorators.csrf import csrf_exempt
-from django.views.generic import DetailView, ListView, TemplateView, View
+from django.views.generic import DetailView, ListView
import arrow
-from icalendar import Calendar, Event, vText
-
-from coderdojochi.forms import (
- CDCForm,
- CDCModelForm,
- ContactForm,
- DonationForm,
- GuardianForm,
- MentorForm,
- SignupForm,
- StudentForm,
-)
-from coderdojochi.mixins import RoleRedirectMixin
-from coderdojochi.models import (
- Donation,
- Equipment,
- EquipmentType,
- Guardian,
- Meeting,
- MeetingOrder,
- Mentor,
- MentorOrder,
- Order,
- PartnerPasswordAccess,
- Session,
- Student,
-)
+
+from coderdojochi.models import Meeting, MeetingOrder, Mentor
from coderdojochi.util import email
from coderdojochi.views.calendar import CalendarView
@@ -64,9 +26,7 @@ class MeetingsView(ListView):
template_name = "meetings.html"
def get_queryset(self):
- objects = self.model.objects.filter(
- end_date__gte=timezone.now()
- )
+ objects = self.model.objects.filter(end_date__gte=timezone.now())
if not self.request.user.is_authenticated:
objects = objects.filter(is_public=True)
@@ -79,7 +39,7 @@ def get_queryset(self):
class MeetingDetailView(DetailView):
model = Meeting
- template_name = "meeting-detail.html"
+ template_name = "meeting_detail.html"
def get_queryset(self):
objects = self.model.objects.filter()
@@ -96,7 +56,7 @@ def get(self, request, *args, **kwargs):
try:
self.object = self.get_object()
except Http404:
- return redirect('meetings')
+ return redirect("meetings")
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
@@ -106,36 +66,35 @@ def get_context_data(self, **kwargs):
user = self.request.user
- if user.is_authenticated and user.role == 'mentor':
+ if user.is_authenticated and user.role == "mentor":
mentor = get_object_or_404(Mentor, user=self.request.user)
- active_meeting_orders = MeetingOrder.objects.filter(
- meeting=self.object,
- is_active=True
- )
- context['active_meeting_orders'] = active_meeting_orders
- context['mentor_signed_up'] = active_meeting_orders.filter(
- mentor=mentor
- ).exists()
+ active_meeting_orders = MeetingOrder.objects.filter(meeting=self.object, is_active=True)
+ context["active_meeting_orders"] = active_meeting_orders
+ context["mentor_signed_up"] = active_meeting_orders.filter(mentor=mentor).exists()
return context
class MeetingCalendarView(CalendarView):
- event_type = 'meeting'
- event_kwarg = 'pk'
+ event_type = "meeting"
+ event_kwarg = "pk"
event_class = Meeting
def get_summary(self, request, event_obj):
- event_name = f"{event_obj.meeting_type.code} - " if event_obj.meeting_type.code else ''
+ if event_obj.meeting_type.code:
+ event_name = f"{event_obj.meeting_type.code} - "
+ else:
+ event_name = ""
+
event_name += event_obj.meeting_type.title
return f"We All Code: {event_name}"
def get_dtstart(self, request, event_obj):
- return arrow.get(event_obj.start_date).format('YYYYMMDDTHHmmss')
+ return arrow.get(event_obj.start_date).format("YYYYMMDDTHHmmss")
def get_dtend(self, request, event_obj):
- return arrow.get(event_obj.end_date).format('YYYYMMDDTHHmmss')
+ return arrow.get(event_obj.end_date).format("YYYYMMDDTHHmmss")
def get_description(self, event_obj):
return strip_tags(event_obj.meeting_type.description)
@@ -144,129 +103,101 @@ def get_location(self, request, event_obj):
pass
-def meeting_sign_up(request, pk, template_name="meeting-sign-up.html"):
+def meeting_sign_up(request, pk, template_name="meeting_sign_up.html"):
meeting_obj = get_object_or_404(Meeting, pk=pk)
- mentor = get_object_or_404(
- Mentor,
- user=request.user
- )
+ mentor = get_object_or_404(Mentor, user=request.user)
- meeting_orders = MeetingOrder.objects.filter(
- meeting=meeting_obj,
- is_active=True
- )
+ meeting_orders = MeetingOrder.objects.filter(meeting=meeting_obj, is_active=True)
user_meeting_order = meeting_orders.filter(mentor=mentor)
- user_signed_up = True if user_meeting_order.count() else False
+ if user_meeting_order.count():
+ user_signed_up = True
+ else:
+ user_signed_up = False
- if request.method == 'POST':
+ if request.method == "POST":
if user_signed_up:
- meeting_order = get_object_or_404(
- MeetingOrder,
- meeting=meeting_obj,
- mentor=mentor
- )
+ meeting_order = get_object_or_404(MeetingOrder, meeting=meeting_obj, mentor=mentor)
meeting_order.is_active = False
meeting_order.save()
- messages.success(
- request,
- 'Thanks for letting us know!'
- )
+ messages.success(request, "Thanks for letting us know!")
else:
if not settings.DEBUG:
- ip = (
- request.META['HTTP_X_FORWARDED_FOR'] or
- request.META['REMOTE_ADDR']
- )
+ ip = request.META["HTTP_X_FORWARDED_FOR"] or request.META["REMOTE_ADDR"]
else:
- ip = request.META['REMOTE_ADDR']
+ ip = request.META["REMOTE_ADDR"]
- meeting_order, created = MeetingOrder.objects.get_or_create(
- mentor=mentor,
- meeting=meeting_obj
- )
+ meeting_order, created = MeetingOrder.objects.get_or_create(mentor=mentor, meeting=meeting_obj)
meeting_order.ip = ip
meeting_order.is_active = True
meeting_order.save()
- messages.success(
- request,
- 'Success! See you there!'
- )
+ messages.success(request, "Success! See you there!")
merge_global_data = {
- 'first_name': request.user.first_name,
- 'last_name': request.user.last_name,
- 'order_id': meeting_order.id,
- 'meeting_title': meeting_obj.meeting_type.title,
- 'meeting_description': meeting_obj.meeting_type.description,
- 'meeting_start_date': arrow.get(meeting_obj.start_date).to('local').format('dddd, MMMM D, YYYY'),
- 'meeting_start_time': arrow.get(meeting_obj.start_date).to('local').format('h:mma'),
- 'meeting_end_date': arrow.get(meeting_obj.end_date).to('local').format('dddd, MMMM D, YYYY'),
- 'meeting_end_time': arrow.get(meeting_obj.end_date).to('local').format('h:mma'),
- 'meeting_location_name': meeting_obj.location.name,
- 'meeting_location_address': meeting_obj.location.address,
- 'meeting_location_city': meeting_obj.location.city,
- 'meeting_location_state': meeting_obj.location.state,
- 'meeting_location_zip': meeting_obj.location.zip,
- 'meeting_additional_info': meeting_obj.additional_info,
- 'meeting_url': f"{settings.SITE_URL}{meeting_obj.get_absolute_url()}",
- 'meeting_calendar_url': f"{settings.SITE_URL}{meeting_obj.get_calendar_url()}",
- 'microdata_start_date': arrow.get(meeting_obj.start_date).to('local').isoformat(),
- 'microdata_end_date': arrow.get(meeting_obj.end_date).to('local').isoformat(),
+ "first_name": request.user.first_name,
+ "last_name": request.user.last_name,
+ "order_id": meeting_order.id,
+ "meeting_title": meeting_obj.meeting_type.title,
+ "meeting_description": meeting_obj.meeting_type.description,
+ "meeting_start_date": arrow.get(meeting_obj.start_date).to("local").format("dddd, MMMM D, YYYY"),
+ "meeting_start_time": arrow.get(meeting_obj.start_date).to("local").format("h:mma"),
+ "meeting_end_date": arrow.get(meeting_obj.end_date).to("local").format("dddd, MMMM D, YYYY"),
+ "meeting_end_time": arrow.get(meeting_obj.end_date).to("local").format("h:mma"),
+ "meeting_location_name": meeting_obj.location.name,
+ "meeting_location_address": meeting_obj.location.address,
+ "meeting_location_city": meeting_obj.location.city,
+ "meeting_location_state": meeting_obj.location.state,
+ "meeting_location_zip": meeting_obj.location.zip,
+ "meeting_additional_info": meeting_obj.additional_info,
+ "meeting_url": f"{settings.SITE_URL}{meeting_obj.get_absolute_url()}",
+ "meeting_calendar_url": f"{settings.SITE_URL}{meeting_obj.get_calendar_url()}",
+ "microdata_start_date": arrow.get(meeting_obj.start_date).to("local").isoformat(),
+ "microdata_end_date": arrow.get(meeting_obj.end_date).to("local").isoformat(),
}
email(
- subject='Upcoming mentor meeting confirmation',
- template_name='meeting-confirm-mentor',
+ subject="Upcoming mentor meeting confirmation",
+ template_name="meeting_confirm_mentor",
merge_global_data=merge_global_data,
recipients=[request.user.email],
- preheader=(
- f"Thanks for signing up for our next meeting, {request.user.first_name}. "
- f"We look forward to seeing there."
- ),
+ preheader=f"Thanks for signing up for our next meeting, {request.user.first_name}. We look forward to seeing there.",
)
- return redirect('meeting-detail', meeting_obj.id)
+ return redirect("meeting_detail", meeting_obj.id)
- return render(request, template_name, {
- 'meeting': meeting_obj,
- 'user_signed_up': user_signed_up
- })
+ return render(request, template_name, {"meeting": meeting_obj, "user_signed_up": user_signed_up})
def meeting_announce(request, pk):
if not request.user.is_staff:
- messages.error(
- request,
- 'You do not have permission to access this page.'
- )
- return redirect('home')
+ messages.error(request, "You do not have permission to access this page.")
+ return redirect("home")
meeting_obj = get_object_or_404(Meeting, pk=pk)
if not meeting_obj.announced_date:
merge_data = {}
merge_global_data = {
- 'meeting_title': meeting_obj.meeting_type.title,
- 'meeting_description': meeting_obj.meeting_type.description,
- 'meeting_start_date': arrow.get(meeting_obj.start_date).to('local').format('dddd, MMMM D, YYYY'),
- 'meeting_start_time': arrow.get(meeting_obj.start_date).to('local').format('h:mma'),
- 'meeting_end_date': arrow.get(meeting_obj.end_date).to('local').format('dddd, MMMM D, YYYY'),
- 'meeting_end_time': arrow.get(meeting_obj.end_date).to('local').format('h:mma'),
- 'meeting_location_name': meeting_obj.location.name,
- 'meeting_location_address': meeting_obj.location.address,
- 'meeting_location_city': meeting_obj.location.city,
- 'meeting_location_state': meeting_obj.location.state,
- 'meeting_location_zip': meeting_obj.location.zip,
- 'meeting_additional_info': meeting_obj.additional_info,
- 'meeting_url': f"{settings.SITE_URL}{meeting_obj.get_absolute_url()}",
- 'meeting_calendar_url': f"{settings.SITE_URL}{meeting_obj.get_calendar_url()}",
+ "meeting_title": meeting_obj.meeting_type.title,
+ "meeting_description": meeting_obj.meeting_type.description,
+ "meeting_start_date": arrow.get(meeting_obj.start_date).to("local").format("dddd, MMMM D, YYYY"),
+ "meeting_start_time": arrow.get(meeting_obj.start_date).to("local").format("h:mma"),
+ "meeting_end_date": arrow.get(meeting_obj.end_date).to("local").format("dddd, MMMM D, YYYY"),
+ "meeting_end_time": arrow.get(meeting_obj.end_date).to("local").format("h:mma"),
+ "meeting_location_name": meeting_obj.location.name,
+ "meeting_location_address": meeting_obj.location.address,
+ "meeting_location_city": meeting_obj.location.city,
+ "meeting_location_state": meeting_obj.location.state,
+ "meeting_location_zip": meeting_obj.location.zip,
+ "meeting_additional_info": meeting_obj.additional_info,
+ "meeting_url": f"{settings.SITE_URL}{meeting_obj.get_absolute_url()}",
+ "meeting_calendar_url": f"{settings.SITE_URL}{meeting_obj.get_calendar_url()}",
}
mentors = Mentor.objects.filter(
@@ -276,32 +207,26 @@ def meeting_announce(request, pk):
recipients = []
for mentor in mentors:
- recipients.append(mentor.user.email)
- merge_data[mentor.user.email] = {
- 'first_name': mentor.user.first_name,
- 'last_name': mentor.user.last_name,
+ recipients.append(mentor.email)
+ merge_data[mentor.email] = {
+ "first_name": mentor.first_name,
+ "last_name": mentor.last_name,
}
email(
- subject='New meeting announced!',
- template_name='meeting-announcement-mentor',
+ subject="New meeting announced!",
+ template_name="meeting_announcement_mentor",
merge_data=merge_data,
merge_global_data=merge_global_data,
recipients=recipients,
- preheader='A new meeting has been announced. Come join us for some amazing fun!',
+ preheader="A new meeting has been announced. Come join us for some amazing fun!",
)
meeting_obj.announced_date = timezone.now()
meeting_obj.save()
- messages.success(
- request,
- f"Meeting announced to {mentors.count()} mentors."
- )
+ messages.success(request, f"Meeting announced to {mentors.count()} mentors.")
else:
- messages.warning(
- request,
- 'Meeting already announced.'
- )
+ messages.warning(request, "Meeting already announced.")
- return redirect('cdc-admin')
+ return redirect("cdc-admin")
diff --git a/coderdojochi/views/mentor/__init__.py b/coderdojochi/views/mentor/__init__.py
new file mode 100644
index 00000000..494eed44
--- /dev/null
+++ b/coderdojochi/views/mentor/__init__.py
@@ -0,0 +1 @@
+from .sessions import *
diff --git a/coderdojochi/views/mentor/sessions.py b/coderdojochi/views/mentor/sessions.py
new file mode 100644
index 00000000..6234f5ed
--- /dev/null
+++ b/coderdojochi/views/mentor/sessions.py
@@ -0,0 +1,26 @@
+from django.shortcuts import get_object_or_404
+from django.views.generic import DetailView
+
+from ...models import Mentor, MentorOrder, Session
+
+
+class SessionDetailView(DetailView):
+ model = Session
+ template_name = "mentor/session_detail.html"
+
+ def get_context_data(self, **kwargs):
+ session = self.object
+ mentor = get_object_or_404(Mentor, user=self.request.user)
+
+ session_orders = MentorOrder.objects.filter(
+ session=session,
+ mentor=mentor,
+ is_active=True,
+ )
+
+ context = super().get_context_data(**kwargs)
+ context["mentor_signed_up"] = session_orders.exists()
+ context["spots_remaining"] = session.get_mentor_capacity() - session_orders.count()
+ context["account"] = mentor
+
+ return context
diff --git a/coderdojochi/views/profile.py b/coderdojochi/views/profile.py
index ce48f61f..6c5f9b58 100644
--- a/coderdojochi/views/profile.py
+++ b/coderdojochi/views/profile.py
@@ -1,52 +1,15 @@
-import calendar
import logging
-import operator
-from collections import Counter
-from datetime import date, timedelta
-from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
-from django.core.exceptions import ObjectDoesNotExist
-from django.core.mail import EmailMultiAlternatives, get_connection
-from django.db.models import Case, Count, IntegerField, When
-from django.http import HttpResponse
-from django.shortcuts import get_object_or_404, redirect, render
-from django.urls import reverse
+from django.shortcuts import get_object_or_404, redirect
from django.utils import timezone
from django.utils.decorators import method_decorator
-from django.utils.html import strip_tags
-from django.views.decorators.cache import never_cache
-from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView
-import arrow
-from icalendar import Calendar, Event, vText
-
-from coderdojochi.forms import (
- CDCModelForm,
- ContactForm,
- DonationForm,
- GuardianForm,
- MentorForm,
- StudentForm,
-)
-from coderdojochi.models import (
- Donation,
- Equipment,
- EquipmentType,
- Guardian,
- Meeting,
- MeetingOrder,
- Mentor,
- MentorOrder,
- Order,
- PartnerPasswordAccess,
- Session,
- Student,
-)
-from coderdojochi.util import email
+from coderdojochi.forms import CDCModelForm, MentorForm
+from coderdojochi.models import Mentor, MentorOrder
logger = logging.getLogger(__name__)
@@ -55,7 +18,7 @@
class DojoMentorView(TemplateView):
- template_name = 'mentor/dojo.html'
+ template_name = "mentor/dojo.html"
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
@@ -63,77 +26,54 @@ def dispatch(self, request, *args, **kwargs):
def get_context_data(self, **kwargs):
context = super(DojoMentorView, self).get_context_data(**kwargs)
- context['highlight'] = self.request.GET.get('highlight', False)
+ context["highlight"] = self.request.GET.get("highlight", False)
mentor = get_object_or_404(Mentor, user=self.request.user)
- context['mentor'] = mentor
+ context["mentor"] = mentor
orders = MentorOrder.objects.select_related().filter(
is_active=True,
- mentor=context['mentor'],
+ mentor=context["mentor"],
)
- upcoming_sessions = orders.filter(
- is_active=True,
- session__start_date__gte=timezone.now()
- ).order_by('session__start_date')
-
- past_sessions = orders.filter(
- is_active=True,
- session__start_date__lte=timezone.now()
- ).order_by('session__start_date')
+ # upcoming_sessions = orders.filter(is_active=True, session__start_date__gte=timezone.now()).order_by(
+ # "session__start_date"
+ # )
- meeting_orders = MeetingOrder.objects.select_related().filter(
- mentor=mentor
+ past_sessions = orders.filter(is_active=True, session__start_date__lte=timezone.now()).order_by(
+ "session__start_date"
)
- upcoming_meetings = meeting_orders.filter(
- is_active=True,
- meeting__is_public=True,
- meeting__end_date__gte=timezone.now()
- ).order_by('meeting__start_date')
+ # meeting_orders = MeetingOrder.objects.select_related().filter(mentor=mentor)
+
+ # upcoming_meetings = meeting_orders.filter(
+ # is_active=True, meeting__is_public=True, meeting__end_date__gte=timezone.now()
+ # ).order_by("meeting__start_date")
- context['account_complete'] = False
+ context["account_complete"] = False
if (
- mentor.user.first_name and
- mentor.user.last_name and
- mentor.avatar and
- mentor.background_check and
- past_sessions.count() > 0
+ mentor.first_name
+ and mentor.last_name
+ and mentor.avatar
+ and mentor.background_check
+ and past_sessions.count() > 0
):
- context['account_complete'] = True
+ context["account_complete"] = True
return context
def post(self, request, *args, **kwargs):
mentor = get_object_or_404(Mentor, user=request.user)
- form = MentorForm(
- request.POST,
- request.FILES,
- instance=mentor
- )
+ form = MentorForm(request.POST, request.FILES, instance=mentor)
- user_form = CDCModelForm(
- request.POST,
- request.FILES,
- instance=mentor.user
- )
+ user_form = CDCModelForm(request.POST, request.FILES, instance=mentor.user)
- if (
- form.is_valid() and
- user_form.is_valid()
- ):
+ if form.is_valid() and user_form.is_valid():
form.save()
user_form.save()
- messages.success(
- request,
- 'Profile information saved.'
- )
+ messages.success(request, "Profile information saved.")
- return redirect('account_home')
+ return redirect("account_home")
else:
- messages.error(
- request,
- 'There was an error. Please try again.'
- )
+ messages.error(request, "There was an error. Please try again.")
diff --git a/coderdojochi/views/public/__init__.py b/coderdojochi/views/public/__init__.py
new file mode 100644
index 00000000..3d4ea842
--- /dev/null
+++ b/coderdojochi/views/public/__init__.py
@@ -0,0 +1,2 @@
+from .mentor import *
+from .sessions import *
diff --git a/coderdojochi/views/mentor.py b/coderdojochi/views/public/mentor.py
similarity index 96%
rename from coderdojochi/views/mentor.py
rename to coderdojochi/views/public/mentor.py
index 732aee63..ec9aa82c 100644
--- a/coderdojochi/views/mentor.py
+++ b/coderdojochi/views/public/mentor.py
@@ -1,6 +1,6 @@
from django.views.generic import DetailView, ListView
-from ..models import Mentor
+from ...models import Mentor
class MentorListView(ListView):
diff --git a/coderdojochi/views/public/sessions.py b/coderdojochi/views/public/sessions.py
new file mode 100644
index 00000000..86c07ebf
--- /dev/null
+++ b/coderdojochi/views/public/sessions.py
@@ -0,0 +1,14 @@
+from django.shortcuts import get_object_or_404
+from django.views.generic import DetailView
+
+from ...models import Session
+
+
+class SessionDetailView(DetailView):
+ model = Session
+ template_name = "public/session_detail.html"
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+
+ return context
diff --git a/coderdojochi/views/sessions.py b/coderdojochi/views/sessions.py
index a8164609..12c6745f 100644
--- a/coderdojochi/views/sessions.py
+++ b/coderdojochi/views/sessions.py
@@ -1,5 +1,4 @@
import logging
-from datetime import date
from django.conf import settings
from django.contrib import messages
@@ -10,23 +9,17 @@
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.html import strip_tags
-from django.views.generic import TemplateView
+from django.views.generic import DetailView, TemplateView, View
+from django.views.generic.base import RedirectView
import arrow
-from dateutil.relativedelta import relativedelta
from coderdojochi.mixins import RoleRedirectMixin, RoleTemplateMixin
-from coderdojochi.models import (
- Guardian,
- Mentor,
- MentorOrder,
- Order,
- PartnerPasswordAccess,
- Session,
- Student,
-)
+from coderdojochi.models import Guardian, Mentor, MentorOrder, Order, PartnerPasswordAccess, Session, Student, guardian
from coderdojochi.util import email
-from coderdojochi.views.calendar import CalendarView
+
+from . import guardian, mentor, public
+from .calendar import CalendarView
logger = logging.getLogger(__name__)
@@ -36,35 +29,35 @@
def session_confirm_mentor(request, session_obj, order):
merge_global_data = {
- 'first_name': request.user.first_name,
- 'last_name': request.user.last_name,
- 'class_code': session_obj.course.code,
- 'class_title': session_obj.course.title,
- 'class_description': session_obj.course.description,
- 'class_start_date': arrow.get(session_obj.mentor_start_date).to('local').format('dddd, MMMM D, YYYY'),
- 'class_start_time': arrow.get(session_obj.mentor_start_date).to('local').format('h:mma'),
- 'class_end_date': arrow.get(session_obj.mentor_end_date).to('local').format('dddd, MMMM D, YYYY'),
- 'class_end_time': arrow.get(session_obj.mentor_end_date).to('local').format('h:mma'),
- 'class_location_name': session_obj.location.name,
- 'class_location_address': session_obj.location.address,
- 'class_location_city': session_obj.location.city,
- 'class_location_state': session_obj.location.state,
- 'class_location_zip': session_obj.location.zip,
- 'class_additional_info': session_obj.additional_info,
- 'class_url': f"{settings.SITE_URL}{session_obj.get_absolute_url()}",
- 'class_calendar_url': f"{settings.SITE_URL}{session_obj.get_calendar_url()}",
- 'microdata_start_date': arrow.get(session_obj.mentor_start_date).to('local').isoformat(),
- 'microdata_end_date': arrow.get(session_obj.mentor_end_date).to('local').isoformat(),
- 'order_id': order.id,
- 'online_video_link': session_obj.online_video_link,
- 'online_video_description': session_obj.online_video_description,
+ "first_name": request.user.first_name,
+ "last_name": request.user.last_name,
+ "class_code": session_obj.course.code,
+ "class_title": session_obj.course.title,
+ "class_description": session_obj.course.description,
+ "class_start_date": arrow.get(session_obj.mentor_start_date).to("local").format("dddd, MMMM D, YYYY"),
+ "class_start_time": arrow.get(session_obj.mentor_start_date).to("local").format("h:mma"),
+ "class_end_date": arrow.get(session_obj.mentor_end_date).to("local").format("dddd, MMMM D, YYYY"),
+ "class_end_time": arrow.get(session_obj.mentor_end_date).to("local").format("h:mma"),
+ "class_location_name": session_obj.location.name,
+ "class_location_address": session_obj.location.address,
+ "class_location_city": session_obj.location.city,
+ "class_location_state": session_obj.location.state,
+ "class_location_zip": session_obj.location.zip,
+ "class_additional_info": session_obj.additional_info,
+ "class_url": f"{settings.SITE_URL}{session_obj.get_absolute_url()}",
+ "class_calendar_url": f"{settings.SITE_URL}{session_obj.get_calendar_url()}",
+ "microdata_start_date": arrow.get(session_obj.mentor_start_date).to("local").isoformat(),
+ "microdata_end_date": arrow.get(session_obj.mentor_end_date).to("local").isoformat(),
+ "order_id": order.id,
+ "online_video_link": session_obj.online_video_link,
+ "online_video_description": session_obj.online_video_description,
}
email(
- subject='Mentoring confirmation for {} class'.format(
- arrow.get(session_obj.mentor_start_date).to('local').format('MMMM D'),
+ subject="Mentoring confirmation for {} class".format(
+ arrow.get(session_obj.mentor_start_date).to("local").format("MMMM D"),
),
- template_name='class-confirm-mentor',
+ template_name="class_confirm_mentor",
merge_global_data=merge_global_data,
recipients=[request.user.email],
preheader="It's time to use your powers for good.",
@@ -73,274 +66,238 @@ def session_confirm_mentor(request, session_obj, order):
def session_confirm_guardian(request, session_obj, order, student):
merge_global_data = {
- 'first_name': request.user.first_name,
- 'last_name': request.user.last_name,
- 'student_first_name': student.first_name,
- 'student_last_name': student.last_name,
- 'class_code': session_obj.course.code,
- 'class_title': session_obj.course.title,
- 'class_description': session_obj.course.description,
- 'class_start_date': arrow.get(session_obj.start_date).to('local').format('dddd, MMMM D, YYYY'),
- 'class_start_time': arrow.get(session_obj.start_date).to('local').format('h:mma'),
- 'class_end_date': arrow.get(session_obj.end_date).to('local').format('dddd, MMMM D, YYYY'),
- 'class_end_time': arrow.get(session_obj.end_date).to('local').format('h:mma'),
- 'class_location_name': session_obj.location.name,
- 'class_location_address': session_obj.location.address,
- 'class_location_city': session_obj.location.city,
- 'class_location_state': session_obj.location.state,
- 'class_location_zip': session_obj.location.zip,
- 'class_additional_info': session_obj.additional_info,
- 'class_url': session_obj.get_absolute_url(),
- 'class_calendar_url': session_obj.get_calendar_url(),
- 'microdata_start_date': arrow.get(session_obj.start_date).to('local').isoformat(),
- 'microdata_end_date': arrow.get(session_obj.end_date).to('local').isoformat(),
- 'order_id': order.id,
- 'online_video_link': session_obj.online_video_link,
- 'online_video_description': session_obj.online_video_description,
+ "first_name": request.user.first_name,
+ "last_name": request.user.last_name,
+ "student_first_name": student.first_name,
+ "student_last_name": student.last_name,
+ "class_code": session_obj.course.code,
+ "class_title": session_obj.course.title,
+ "class_description": session_obj.course.description,
+ "class_start_date": arrow.get(session_obj.start_date).to("local").format("dddd, MMMM D, YYYY"),
+ "class_start_time": arrow.get(session_obj.start_date).to("local").format("h:mma"),
+ "class_end_date": arrow.get(session_obj.end_date).to("local").format("dddd, MMMM D, YYYY"),
+ "class_end_time": arrow.get(session_obj.end_date).to("local").format("h:mma"),
+ "class_location_name": session_obj.location.name,
+ "class_location_address": session_obj.location.address,
+ "class_location_city": session_obj.location.city,
+ "class_location_state": session_obj.location.state,
+ "class_location_zip": session_obj.location.zip,
+ "class_additional_info": session_obj.additional_info,
+ "class_url": session_obj.get_absolute_url(),
+ "class_calendar_url": session_obj.get_calendar_url(),
+ "microdata_start_date": arrow.get(session_obj.start_date).to("local").isoformat(),
+ "microdata_end_date": arrow.get(session_obj.end_date).to("local").isoformat(),
+ "order_id": order.id,
+ "online_video_link": session_obj.online_video_link,
+ "online_video_description": session_obj.online_video_description,
}
email(
- subject=f'Upcoming class confirmation for {student.first_name} {student.last_name}',
- template_name='class-confirm-guardian',
+ subject=f"Upcoming class confirmation for {student.full_name}",
+ template_name="class_confirm_guardian",
merge_global_data=merge_global_data,
recipients=[request.user.email],
- preheader='Magical wizards have generated this confirmation. All thanks to the mystical power of coding.',
+ preheader="Magical wizards have generated this confirmation. All thanks to the mystical power of coding.",
)
-class SessionDetailView(RoleRedirectMixin, RoleTemplateMixin, TemplateView):
- template_name = "session-detail.html"
-
- def dispatch(self, request, *args, **kwargs):
- session_obj = get_object_or_404(Session, id=kwargs['pk'])
-
- if request.method == 'GET':
- if session_obj.password and not self.validate_partner_session_access(self.request, kwargs['pk']):
- return redirect(reverse('session-password', kwargs=kwargs))
-
- if request.user.is_authenticated and request.user.role:
- if 'enroll' in request.GET or 'enroll' in kwargs:
- return self.enroll_redirect(request, session_obj)
-
- kwargs['session_obj'] = session_obj
- return super(SessionDetailView, self).dispatch(request, *args, **kwargs)
-
- def enroll_redirect(self, request, session_obj):
- if request.user.role == 'mentor':
- return redirect('session-sign-up', pk=session_obj.id)
-
- guardian = get_object_or_404(Guardian, user=request.user)
- student = get_object_or_404(Student, guardian=guardian, id=(int(request.GET['student'])))
-
- if student:
- return redirect('session-sign-up', pk=session_obj.id, student_id=student.id)
-
- return redirect(f"{reverse('welcome')}?next={session_obj.get_absolute_url()}&enroll=True")
-
- def validate_partner_session_access(self, request, pk):
- authed_sessions = request.session.get('authed_partner_sessions')
-
- if authed_sessions and pk in authed_sessions:
- if request.user.is_authenticated:
- PartnerPasswordAccess.objects.get_or_create(
- session_id=pk,
- user=request.user
- )
- return True
-
+class SessionDetailView(View):
+ def get(self, request, *args, **kwargs):
if request.user.is_authenticated:
- try:
- PartnerPasswordAccess.objects.get(
- session_id=pk,
- user_id=request.user.id
- )
- except PartnerPasswordAccess.DoesNotExist:
- return False
- else:
- return True
-
- else:
- return False
-
- def get_context_data(self, **kwargs):
- context = super(SessionDetailView, self).get_context_data(**kwargs)
- session_obj = kwargs['session_obj']
- context['session'] = session_obj
-
- upcoming_classes = Session.objects.filter(
- is_active=True,
- start_date__gte=timezone.now()
- ).order_by('start_date')
- context['upcoming_classes'] = upcoming_classes
-
- active_mentors = Mentor.objects.filter(
- id__in=MentorOrder.objects.filter(
- session=session_obj,
- is_active=True
- ).values('mentor__id')
- )
- context['active_mentors'] = active_mentors
-
- if self.request.user.is_authenticated:
- if self.request.user.role == 'mentor':
- account = get_object_or_404(Mentor, user=self.request.user)
- session_orders = MentorOrder.objects.filter(
- session=session_obj,
- is_active=True,
- )
- context['mentor_signed_up'] = session_orders.filter(
- mentor=account
- ).exists()
-
- context['spots_remaining'] = (
- session_obj.get_mentor_capacity() - session_orders.count()
- )
+ if request.user.role == "mentor":
+ return mentor.SessionDetailView.as_view()(request, *args, **kwargs)
else:
- account = get_object_or_404(Guardian, user=self.request.user)
- context['students'] = account.get_students()
- context['spots_remaining'] = (
- session_obj.capacity -
- session_obj.get_current_students().count()
- )
- context['account'] = account
- else:
- context['upcoming_classes'] = upcoming_classes.filter(is_public=True)
- context['spots_remaining'] = (
- session_obj.capacity -
- session_obj.get_current_students().count()
- )
-
- return context
-
- def post(self, request, *args, **kwargs):
- session_obj = kwargs['session_obj']
- if 'waitlist' not in request.POST:
- messages.error(request, 'Invalid request, please try again.')
- return redirect(session_obj.get_absolute_url())
-
- if request.POST['waitlist'] == 'student':
- account = Student.objects.get(id=request.POST['account_id'])
- waitlist_attr = 'waitlist_students'
- else:
- account = Guardian.objects.get(id=request.POST['account_id'])
- waitlist_attr = 'waitlist_guardians'
-
- if request.POST['remove'] == 'true':
- getattr(session_obj, waitlist_attr).remove(account)
- session_obj.save()
- messages.success(
- request,
- 'You have been removed from the waitlist. Thanks for letting us know.'
- )
- else:
- getattr(session_obj, waitlist_attr).add(account)
- session_obj.save()
- messages.success(
- request,
- 'Added to waitlist successfully.'
- )
- return redirect(session_obj.get_absolute_url())
+ return guardian.SessionDetailView.as_view()(request, *args, **kwargs)
+ return public.SessionDetailView.as_view()(request, *args, **kwargs)
+
+
+# class SessionDetailView(RoleRedirectMixin, RoleTemplateMixin, TemplateView):
+# template_name = "session_detail.html"
+
+# def dispatch(self, request, *args, **kwargs):
+# session_obj = get_object_or_404(Session, id=kwargs["pk"])
+
+# if request.method == "GET":
+# if session_obj.password and not self.validate_partner_session_access(self.request, kwargs["pk"]):
+# return redirect(reverse("session-password", kwargs=kwargs))
+
+# if request.user.is_authenticated and request.user.role:
+# if "enroll" in request.GET or "enroll" in kwargs:
+# return self.enroll_redirect(request, session_obj)
+
+# # kwargs["session_obj"] = session_obj
+# return super(SessionDetailView, self).dispatch(request, *args, **kwargs)
+
+# def enroll_redirect(self, request, session_obj):
+# if request.user.role == "mentor":
+# return redirect("session-sign-up", pk=session_obj.id)
+
+# guardian = get_object_or_404(Guardian, user=request.user)
+# student = get_object_or_404(Student, guardian=guardian, id=(int(request.GET["student"])))
+
+# if student:
+# return redirect("session-sign-up", pk=session_obj.id, student_id=student.id)
+
+# return redirect(f"{reverse('welcome')}?next={session_obj.get_absolute_url()}&enroll=True")
+
+# def validate_partner_session_access(self, request, pk):
+# authed_sessions = request.session.get("authed_partner_sessions")
+
+# if authed_sessions and pk in authed_sessions:
+# if request.user.is_authenticated:
+# PartnerPasswordAccess.objects.get_or_create(session_id=pk, user=request.user)
+# return True
+
+# if request.user.is_authenticated:
+# try:
+# PartnerPasswordAccess.objects.get(session_id=pk, user_id=request.user.id)
+# except PartnerPasswordAccess.DoesNotExist:
+# return False
+# else:
+# return True
+
+# else:
+# return False
+
+# def get_context_data(self, **kwargs):
+# print(kwargs["session_obj"].__dict__)
+# context = super(SessionDetailView, self).get_context_data(**kwargs)
+# session_obj = kwargs["session_obj"]
+# context["session"] = session_obj
+
+# upcoming_classes = Session.objects.filter(is_active=True, start_date__gte=timezone.now()).order_by("start_date")
+# context["upcoming_classes"] = upcoming_classes
+
+# active_mentors = Mentor.objects.filter(
+# id__in=MentorOrder.objects.filter(session=session_obj, is_active=True).values("mentor__id")
+# )
+# context["active_mentors"] = active_mentors
+
+# if self.request.user.is_authenticated:
+# if self.request.user.role == "mentor":
+# account = get_object_or_404(Mentor, user=self.request.user)
+# session_orders = MentorOrder.objects.filter(session=session_obj, is_active=True,)
+# context["mentor_signed_up"] = session_orders.filter(mentor=account).exists()
+
+# context["spots_remaining"] = session_obj.get_mentor_capacity() - session_orders.count()
+# else:
+# account = get_object_or_404(Guardian, user=self.request.user)
+# context["students"] = account.get_students()
+# context["spots_remaining"] = session_obj.capacity - session_obj.get_active_student_count()
+# context["account"] = account
+# else:
+# context["upcoming_classes"] = upcoming_classes.filter(is_public=True)
+# context["spots_remaining"] = session_obj.capacity - session_obj.objects.get_active_student_count()
+
+# return context
+
+# def post(self, request, *args, **kwargs):
+# session_obj = kwargs["session_obj"]
+# if "waitlist" not in request.POST:
+# messages.error(request, "Invalid request, please try again.")
+# return redirect(session_obj.get_absolute_url())
+
+# if request.POST["waitlist"] == "student":
+# account = Student.objects.get(id=request.POST["account_id"])
+# waitlist_attr = "waitlist_students"
+# else:
+# account = Guardian.objects.get(id=request.POST["account_id"])
+# waitlist_attr = "waitlist_guardians"
+
+# if request.POST["remove"] == "true":
+# getattr(session_obj, waitlist_attr).remove(account)
+# session_obj.save()
+# messages.success(request, "You have been removed from the waitlist. Thanks for letting us know.")
+# else:
+# getattr(session_obj, waitlist_attr).add(account)
+# session_obj.save()
+# messages.success(request, "Added to waitlist successfully.")
+# return redirect(session_obj.get_absolute_url())
class SessionSignUpView(RoleRedirectMixin, RoleTemplateMixin, TemplateView):
- template_name = "session-sign-up.html"
+ template_name = "session_sign_up.html"
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
- session_obj = get_object_or_404(Session, id=kwargs['pk'])
- kwargs['session_obj'] = session_obj
+ session_obj = get_object_or_404(Session, id=kwargs["pk"])
+ kwargs["session_obj"] = session_obj
- if request.user.role == 'mentor':
- session_orders = MentorOrder.objects.filter(
- session=session_obj,
- is_active=True
- )
- kwargs['mentor'] = get_object_or_404(Mentor, user=request.user)
- kwargs['user_signed_up'] = session_orders.filter(
- mentor=kwargs['mentor']
- ).exists()
+ if request.user.role == "mentor":
+ session_orders = MentorOrder.objects.filter(session=session_obj, is_active=True)
+ kwargs["mentor"] = get_object_or_404(Mentor, user=request.user)
+ kwargs["user_signed_up"] = session_orders.filter(mentor=kwargs["mentor"]).exists()
- elif request.user.role == 'guardian':
- kwargs['guardian'] = get_object_or_404(Guardian, user=request.user)
- kwargs['student'] = get_object_or_404(Student, id=kwargs['student_id'])
- kwargs['user_signed_up'] = kwargs['student'].is_registered_for_session(session_obj)
+ elif request.user.role == "guardian":
+ kwargs["guardian"] = get_object_or_404(Guardian, user=request.user)
+ kwargs["student"] = get_object_or_404(Student, id=kwargs["student_id"])
+ kwargs["user_signed_up"] = kwargs["student"].is_registered_for_session(session_obj)
access_dict = self.check_access(request, *args, **kwargs)
- if access_dict.get('message'):
- if access_dict.get('redirect') == 'account_home':
- messages.warning(request, access_dict['message'])
+ if access_dict.get("message"):
+ if access_dict.get("redirect") == "account_home":
+ messages.warning(request, access_dict["message"])
else:
- messages.error(request, access_dict['message'])
+ messages.error(request, access_dict["message"])
- return redirect(access_dict['redirect'])
+ return redirect(access_dict["redirect"])
return super(SessionSignUpView, self).dispatch(request, *args, **kwargs)
def check_access(self, request, *args, **kwargs):
- bg_check_link = "https://app.sterlingvolunteers.com/promoorder/3df76c55-9961-46e1-8e5f-f6b38e2ec4dc"
+ BG_CHECK_LINK = "https://app.sterlingvolunteers.com/promoorder/3df76c55-9961-46e1-8e5f-f6b38e2ec4dc"
access_dict = {}
# Returns a message and redirect url if not working as dict
- if kwargs.get('mentor'):
- if not kwargs['mentor'].background_check:
+ if kwargs.get("mentor"):
+ if not kwargs["mentor"].background_check:
access_dict = {
- 'message': (
+ "message": (
"You cannot sign up for a class until you "
- "fill out the background search form ."
+ f'fill out the background search form .'
),
- 'redirect': request.META.get('HTTP_REFERER', '/dojo')
+ "redirect": request.META.get("HTTP_REFERER", "/dojo"),
}
- if kwargs.get('student'):
- limits = self.student_limitations(
- kwargs['student'], kwargs['session_obj'], kwargs['user_signed_up']
- )
+ if kwargs.get("student"):
+ limits = self.student_limitations(kwargs["student"], kwargs["session_obj"], kwargs["user_signed_up"])
if limits:
- access_dict = {
- 'message': limits,
- 'redirect': kwargs['session_obj'].get_absolute_url()
- }
+ access_dict = {"message": limits, "redirect": kwargs["session_obj"].get_absolute_url()}
+
return access_dict
def student_limitations(self, student, session_obj, user_signed_up):
if not student.is_within_gender_limitation(session_obj.gender_limitation):
- return f'Sorry, this class is limited to {session_obj.gender_limitation}s this time around.'
+ return f"Sorry, this class is limited to {session_obj.gender_limitation}s this time around."
if not student.is_within_age_range(session_obj.minimum_age, session_obj.maximum_age):
- return (
- f"Sorry, this class is limited to students between ages "
- f"{session_obj.minimum_age} and {session_obj.maximum_age}."
- )
+ return f"Sorry, this class is limited to students between ages {session_obj.minimum_age} and {session_obj.maximum_age}."
- if not user_signed_up and session_obj.capacity <= session_obj.get_current_students().count():
+ if not user_signed_up and session_obj.capacity <= session_obj.get_active_student_count():
return "Sorry this class has sold out. Please sign up for the wait list and/or check back later."
return False
def get_context_data(self, **kwargs):
context = super(SessionSignUpView, self).get_context_data(**kwargs)
- context['session'] = kwargs['session_obj']
- context['user_signed_up'] = kwargs.get('user_signed_up')
- context['student'] = kwargs.get('student')
+ context["session"] = kwargs["session_obj"]
+ context["user_signed_up"] = kwargs.get("user_signed_up")
+ context["student"] = kwargs.get("student")
return context
def post(self, request, *args, **kwargs):
- session_obj = kwargs['session_obj']
- user_signed_up = kwargs['user_signed_up']
- mentor = kwargs.get('mentor')
- guardian = kwargs.get('guardian')
- student = kwargs.get('student')
+ session_obj = kwargs["session_obj"]
+ user_signed_up = kwargs["user_signed_up"]
+ mentor = kwargs.get("mentor")
+ guardian = kwargs.get("guardian")
+ student = kwargs.get("student")
if user_signed_up:
if mentor:
- order = get_object_or_404(
- MentorOrder,
- mentor=mentor,
- session=session_obj
- )
+ order = get_object_or_404(MentorOrder, mentor=mentor, session=session_obj)
elif student:
order = get_object_or_404(
Order,
@@ -351,12 +308,12 @@ def post(self, request, *args, **kwargs):
order.is_active = False
order.save()
- messages.success(request, 'Thanks for letting us know!')
+ messages.success(request, "Thanks for letting us know!")
else:
- ip = request.META['REMOTE_ADDR']
+ ip = request.META["REMOTE_ADDR"]
if not settings.DEBUG:
- ip = request.META['HTTP_X_FORWARDED_FOR'] or request.META['REMOTE_ADDR']
+ ip = request.META["HTTP_X_FORWARDED_FOR"] or request.META["REMOTE_ADDR"]
if mentor:
order, created = MentorOrder.objects.get_or_create(
@@ -374,7 +331,7 @@ def post(self, request, *args, **kwargs):
order.is_active = True
order.save()
- messages.success(request, 'Success! See you there!')
+ messages.success(request, "Success! See you there!")
if mentor:
session_confirm_mentor(request, session_obj, order)
@@ -385,55 +342,52 @@ def post(self, request, *args, **kwargs):
class PasswordSessionView(TemplateView):
- template_name = 'session-partner-password.html'
+ template_name = "session_partner_password.html"
def get_context_data(self, **kwargs):
context = super(PasswordSessionView, self).get_context_data(**kwargs)
- session_obj = get_object_or_404(Session, id=kwargs.get('pk'))
+ session_obj = get_object_or_404(Session, id=kwargs.get("pk"))
- context['partner_message'] = session_obj.partner_message
+ context["partner_message"] = session_obj.partner_message
return context
def post(self, request, *args, **kwargs):
- session_obj = get_object_or_404(Session, id=kwargs.get('pk'))
- password_input = request.POST.get('password')
+ session_obj = get_object_or_404(Session, id=kwargs.get("pk"))
+ password_input = request.POST.get("password")
context = self.get_context_data(**kwargs)
if not password_input:
- context['error'] = 'Must enter a password.'
+ context["error"] = "Must enter a password."
return render(request, self.template_name, context)
if session_obj.password != password_input:
- context['error'] = 'Invalid password.'
+ context["error"] = "Invalid password."
return render(request, self.template_name, context)
# Get from user session or create an empty set
- authed_partner_sessions = request.session.get('authed_partner_sessions', [])
+ authed_partner_sessions = request.session.get("authed_partner_sessions", [])
# Add course session id to user session
- authed_partner_sessions.append(kwargs.get('pk'))
+ authed_partner_sessions.append(kwargs.get("pk"))
# Remove duplicates
authed_partner_sessions = list(set(authed_partner_sessions))
# Store it.
- request.session['authed_partner_sessions'] = authed_partner_sessions
+ request.session["authed_partner_sessions"] = authed_partner_sessions
if request.user.is_authenticated:
- PartnerPasswordAccess.objects.get_or_create(
- session=session_obj,
- user=request.user
- )
+ PartnerPasswordAccess.objects.get_or_create(session=session_obj, user=request.user)
return redirect(session_obj)
class SessionCalendarView(CalendarView):
- event_type = 'class'
- event_kwarg = 'pk'
+ event_type = "class"
+ event_kwarg = "pk"
event_class = Session
def get_summary(self, request, event_obj):
@@ -442,14 +396,14 @@ def get_summary(self, request, event_obj):
def get_dtstart(self, request, event_obj):
dtstart = f"{arrow.get(event_obj.start_date).format('YYYYMMDDTHHmmss')}Z"
- if request.user.is_authenticated and request.user.role == 'mentor':
+ if request.user.is_authenticated and request.user.role == "mentor":
dtstart = f"{arrow.get(event_obj.mentor_start_date).format('YYYYMMDDTHHmmss')}Z"
return dtstart
def get_dtend(self, request, event_obj):
dtend = f"{arrow.get(event_obj.end_date).format('YYYYMMDDTHHmmss')}Z"
- if request.user.is_authenticated and request.user.role == 'mentor':
+ if request.user.is_authenticated and request.user.role == "mentor":
dtend = f"{arrow.get(event_obj.mentor_end_date).format('YYYYMMDDTHHmmss')}Z"
return dtend
@@ -463,13 +417,11 @@ def get_location(self, request, event_obj):
# If user has a ticket with us, show online link
if event_obj.online_video_link and self.request.user.is_authenticated:
- if self.request.user.role == 'mentor':
+ if self.request.user.role == "mentor":
try:
mentor = Mentor.objects.get(user=self.request.user)
mentor_signed_up = MentorOrder.objects.filter(
- session=event_obj,
- is_active=True,
- mentor=mentor
+ session=event_obj, is_active=True, mentor=mentor
).exists()
if mentor_signed_up:
@@ -478,7 +430,7 @@ def get_location(self, request, event_obj):
except Mentor.DoesNotExist:
pass
- elif self.request.user.role == 'guardian':
+ elif self.request.user.role == "guardian":
try:
guardian = Guardian.objects.get(user=self.request.user)
students = guardian.get_students()
@@ -495,9 +447,6 @@ def get_location(self, request, event_obj):
pass
elif event_obj.location.address:
- location = (
- f"{event_obj.location.name}, {event_obj.location.address}, "
- f"{event_obj.location.city}, {event_obj.location.state}, {event_obj.location.zip}"
- )
+ location = f"{event_obj.location.name}, {event_obj.location.address}, {event_obj.location.city}, {event_obj.location.state}, {event_obj.location.zip}"
return location
diff --git a/coderdojochi/views/welcome.py b/coderdojochi/views/welcome.py
index 01d09843..219a8573 100644
--- a/coderdojochi/views/welcome.py
+++ b/coderdojochi/views/welcome.py
@@ -9,14 +9,7 @@
from django.views.generic import TemplateView
from coderdojochi.forms import GuardianForm, MentorForm, StudentForm
-from coderdojochi.models import (
- Guardian,
- Meeting,
- MeetingOrder,
- Mentor,
- MentorOrder,
- Session,
-)
+from coderdojochi.models import Guardian, Meeting, Mentor, Session
from coderdojochi.util import email
logger = logging.getLogger(__name__)
@@ -27,55 +20,57 @@ class WelcomeView(TemplateView):
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
- next_url = request.GET.get('next')
- kwargs['next_url'] = next_url
+ next_url = request.GET.get("next")
+ kwargs["next_url"] = next_url
# Check for redirect condition on mentor, otherwise pass as kwarg
- if (
- getattr(request.user, 'role', False) == 'mentor'
- and request.method == 'GET'
- ):
+ if getattr(request.user, "role", False) == "mentor" and request.method == "GET":
mentor = get_object_or_404(Mentor, user=request.user)
- if mentor.user.first_name:
- return redirect(next_url if next_url else 'account_home')
- kwargs['mentor'] = mentor
+
+ if mentor.first_name:
+ if next_url:
+ return redirect(next_url)
+ else:
+ return redirect("account_home")
+
+ kwargs["mentor"] = mentor
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user = self.request.user
- mentor = kwargs.get('mentor')
+ mentor = kwargs.get("mentor")
account = False
- role = getattr(user, 'role', False)
+ role = getattr(user, "role", False)
- context['role'] = role
- context['next_url'] = kwargs['next_url']
+ context["role"] = role
+ context["next_url"] = kwargs["next_url"]
if mentor:
account = mentor
- context['form'] = MentorForm(instance=account)
- if role == 'guardian':
+ context["form"] = MentorForm(instance=account)
+ if role == "guardian":
guardian = get_object_or_404(Guardian, user=user)
account = guardian
if not account.phone or not account.zip:
- context['form'] = GuardianForm(instance=account)
+ context["form"] = GuardianForm(instance=account)
else:
- context['add_student'] = True
- context['form'] = StudentForm(initial={'guardian': guardian.pk})
+ context["add_student"] = True
+ context["form"] = StudentForm(initial={"guardian": guardian.pk})
- if account.user.first_name and account.get_students():
- context['students'] = account.get_students().count()
+ if account.first_name and account.get_students():
+ context["students"] = account.get_students().count()
- context['account'] = account
+ context["account"] = account
return context
def post(self, request, *args, **kwargs):
user = request.user
- role = getattr(user, 'role', False)
- next_url = kwargs['next_url']
+ role = getattr(user, "role", False)
+ next_url = kwargs["next_url"]
if role:
- if role == 'mentor':
+ if role == "mentor":
account = get_object_or_404(Mentor, user=user)
return self.update_account(request, account, next_url)
account = get_object_or_404(Guardian, user=user)
@@ -90,26 +85,26 @@ def post(self, request, *args, **kwargs):
def update_account(self, request, account, next_url):
if isinstance(account, Mentor):
form = MentorForm(request.POST, instance=account)
- role = 'mentor'
+ role = "mentor"
else:
form = GuardianForm(request.POST, instance=account)
- role = 'guardian'
+ role = "guardian"
if form.is_valid():
form.save()
- messages.success(request, 'Profile information saved.')
+ messages.success(request, "Profile information saved.")
if next_url:
- if 'enroll' in request.GET:
+ if "enroll" in request.GET:
next_url = f"{next_url}?enroll=True"
else:
- next_url = 'account_home' if isinstance(account, Mentor) else 'welcome'
+ if isinstance(account, Mentor):
+ next_url = "account_home"
+ else:
+ next_url = "welcome"
return redirect(next_url)
- return render(request, self.template_name, {
- 'form': form,
- 'role': role,
- 'account': account,
- 'next_url': next_url
- })
+ return render(
+ request, self.template_name, {"form": form, "role": role, "account": account, "next_url": next_url}
+ )
def add_student(self, request, account, next_url):
form = StudentForm(request.POST)
@@ -117,28 +112,26 @@ def add_student(self, request, account, next_url):
new_student = form.save(commit=False)
new_student.guardian = account
new_student.save()
- messages.success(request, 'Student Registered.')
+ messages.success(request, "Student Registered.")
if next_url:
- if 'enroll' in request.GET:
+ if "enroll" in request.GET:
next_url = f"{next_url}?enroll=True&student={new_student.id}"
else:
- next_url = 'welcome'
+ next_url = "welcome"
return redirect(next_url)
- return render(request, self.template_name, {
- 'form': form,
- 'role': 'guardian',
- 'account': account,
- 'next_url': next_url,
- 'add_student': True
- })
+ return render(
+ request,
+ self.template_name,
+ {"form": form, "role": "guardian", "account": account, "next_url": next_url, "add_student": True},
+ )
def create_new_user(self, request, user, next_url):
- if request.POST.get('role') == 'mentor':
- role = 'mentor'
+ if request.POST.get("role") == "mentor":
+ role = "mentor"
account, created = Mentor.objects.get_or_create(user=user)
else:
- role = 'guardian'
+ role = "guardian"
account, created = Guardian.objects.get_or_create(user=user)
account.user.first_name = user.first_name
@@ -148,47 +141,42 @@ def create_new_user(self, request, user, next_url):
user.role = role
user.save()
- merge_global_data = {
- 'user': user.username,
- 'first_name': user.first_name,
- 'last_name': user.last_name
- }
+ merge_global_data = {"user": user.username, "first_name": user.first_name, "last_name": user.last_name}
- next_url = f"?next={next_url}" if next_url else None
+ if next_url:
+ next_url = f"?next={next_url}"
+ else:
+ next_url = None
- if role == 'mentor':
+ if role == "mentor":
# check for next upcoming meeting
- next_meeting = Meeting.objects.filter(
- is_active=True,
- is_public=True
- ).order_by('start_date').first()
+ next_meeting = Meeting.objects.filter(is_active=True, is_public=True).order_by("start_date").first()
if next_meeting:
- merge_global_data['next_intro_meeting_url'] = f"{settings.SITE_URL}{next_meeting.get_absolute_url()}"
- merge_global_data['next_intro_meeting_calendar_url'] = (
- f"{settings.SITE_URL}{next_meeting.get_calendar_url()}"
- )
+ merge_global_data["next_intro_meeting_url"] = f"{settings.SITE_URL}{next_meeting.get_absolute_url()}"
+ merge_global_data[
+ "next_intro_meeting_calendar_url"
+ ] = f"{settings.SITE_URL}{next_meeting.get_calendar_url()}"
+
if not next_url:
- next_url = reverse('account_home')
+ next_url = reverse("account_home")
else:
# check for next upcoming class
- next_class = Session.objects.filter(
- is_active=True
- ).order_by('start_date').first()
+ next_class = Session.objects.filter(is_active=True).order_by("start_date").first()
if next_class:
- merge_global_data['next_class_url'] = f"{settings.SITE_URL}{next_class.get_absolute_url()}"
- merge_global_data['next_class_calendar_url'] = f"{settings.SITE_URL}{next_class.get_calendar_url()}"
+ merge_global_data["next_class_url"] = f"{settings.SITE_URL}{next_class.get_absolute_url()}"
+ merge_global_data["next_class_calendar_url"] = f"{settings.SITE_URL}{next_class.get_calendar_url()}"
if not next_url:
- next_url = reverse('welcome')
+ next_url = reverse("welcome")
email(
- subject='Welcome!',
- template_name=f"welcome-{role}",
+ subject="Welcome!",
+ template_name=f"welcome_{role}",
merge_global_data=merge_global_data,
recipients=[user.email],
- preheader='Your adventure awaits!',
+ preheader="Your adventure awaits!",
)
return redirect(next_url)
diff --git a/fixtures/17-weallcode.boardmember.json b/fixtures/17-weallcode.boardmember.json
index 38e67fc6..ad061426 100644
--- a/fixtures/17-weallcode.boardmember.json
+++ b/fixtures/17-weallcode.boardmember.json
@@ -7,6 +7,7 @@
"role": "Chair",
"description": "Founder & CEO, Red Squirrel Technologies",
"linkedin": "https://www.linkedin.com/in/redsquirrel/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -21,6 +22,7 @@
"role": "Vice Chair",
"description": "CEO & Principal Consultant, 4 Point Consulting",
"linkedin": "https://www.linkedin.com/in/christynlyons/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -35,6 +37,7 @@
"role": "Secretary",
"description": "Asst. Director of Program Development, GWTP",
"linkedin": "https://www.linkedin.com/in/michael-cotter-mpp-3a3a805/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -49,6 +52,7 @@
"role": "Treasurer",
"description": "Founder & Principal, Blackwood Group LLC",
"linkedin": "https://www.linkedin.com/in/jose-duarte-94469612/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -63,6 +67,7 @@
"role": "Director",
"description": "Founder & CEO, We All Code",
"linkedin": "https://www.linkedin.com/in/karbassi/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T21:48:56.906Z",
diff --git a/fixtures/18-weallcode.associateboardmember.json b/fixtures/18-weallcode.associateboardmember.json
index ca12d719..5ec45817 100644
--- a/fixtures/18-weallcode.associateboardmember.json
+++ b/fixtures/18-weallcode.associateboardmember.json
@@ -7,6 +7,7 @@
"role": "Director",
"description": null,
"linkedin": "https://www.linkedin.com/in/alexandra-rodriguez-beuerman-94990a59/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -21,6 +22,7 @@
"role": "Treasurer",
"description": null,
"linkedin": "https://www.linkedin.com/in/annabkronauer/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -35,6 +37,7 @@
"role": "Vice Chair",
"description": null,
"linkedin": "https://www.linkedin.com/in/brendan-mclaughlin-864b3413/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -49,6 +52,7 @@
"role": "Chair",
"description": null,
"linkedin": "https://www.linkedin.com/in/carolyn-potts-b416a41b/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -63,6 +67,7 @@
"role": "Director",
"description": null,
"linkedin": "https://www.linkedin.com/in/clairelipskey/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -77,6 +82,7 @@
"role": "Director",
"description": null,
"linkedin": "https://www.linkedin.com/in/danielmconrad/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -91,6 +97,7 @@
"role": "Director",
"description": null,
"linkedin": "https://www.linkedin.com/in/jordanpolonsky/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -105,6 +112,7 @@
"role": "Director",
"description": null,
"linkedin": "https://www.linkedin.com/in/joseph-fowler/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -119,6 +127,7 @@
"role": "Director",
"description": null,
"linkedin": "https://www.linkedin.com/in/katherineeevans/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -133,6 +142,7 @@
"role": "Secretary",
"description": null,
"linkedin": "https://www.linkedin.com/in/ltramos7/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -147,6 +157,7 @@
"role": "Director",
"description": null,
"linkedin": "https://www.linkedin.com/in/matthew-felz-53025940/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -161,6 +172,7 @@
"role": "Director",
"description": null,
"linkedin": "https://www.linkedin.com/in/sconstantinides/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
@@ -175,6 +187,7 @@
"role": "Director",
"description": null,
"linkedin": "https://www.linkedin.com/in/tatyanashestopalova/",
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T20:40:00Z",
diff --git a/fixtures/19-weallcode.staffmember.json b/fixtures/19-weallcode.staffmember.json
index 738c6780..1e5524c7 100644
--- a/fixtures/19-weallcode.staffmember.json
+++ b/fixtures/19-weallcode.staffmember.json
@@ -6,6 +6,7 @@
"name": "Ali Karbassi",
"description": null,
"linkedin": null,
+ "join_date": "2020-01-01",
"is_active": true,
"created_at": "2020-05-18T20:40:00Z",
"updated_at": "2020-05-18T21:48:23.037Z",
diff --git a/fixtures/db_views/view_coderdojochi_donation.sql b/fixtures/db_views/view_coderdojochi_donation.sql
new file mode 100644
index 00000000..513a67e4
--- /dev/null
+++ b/fixtures/db_views/view_coderdojochi_donation.sql
@@ -0,0 +1,53 @@
+SELECT
+ a.source,
+ a.id,
+ a.address_country,
+ a.address_city,
+ a.address_name,
+ a.address_state,
+ a.address_zip,
+ a.first_name,
+ a.last_name,
+ a.payer_business_name,
+ a.mc_fee,
+ a.mc_gross,
+ a.payment_date
+FROM
+ (
+ SELECT
+ 'paypal' :: text AS source,
+ paypal_ipn.id,
+ paypal_ipn.address_country,
+ paypal_ipn.address_city,
+ paypal_ipn.address_name,
+ paypal_ipn.address_state,
+ paypal_ipn.address_zip,
+ paypal_ipn.first_name,
+ paypal_ipn.last_name,
+ paypal_ipn.payer_business_name,
+ paypal_ipn.mc_fee,
+ paypal_ipn.mc_gross,
+ paypal_ipn.payment_date
+ FROM
+ paypal_ipn
+ UNION
+ ALL
+ SELECT
+ 'cdc' :: text AS source,
+ coderdojochi_donation.id,
+ NULL :: character varying AS "varchar",
+ NULL :: character varying AS "varchar",
+ NULL :: character varying AS "varchar",
+ NULL :: character varying AS "varchar",
+ NULL :: character varying AS "varchar",
+ coderdojochi_donation.first_name,
+ coderdojochi_donation.last_name,
+ NULL :: character varying AS "varchar",
+ NULL :: numeric AS "numeric",
+ coderdojochi_donation.amount,
+ coderdojochi_donation.created_at
+ FROM
+ coderdojochi_donation
+ ) a
+ORDER BY
+ a.payment_date;
diff --git a/fixtures/db_views/view_coderdojochi_mentororder.sql b/fixtures/db_views/view_coderdojochi_mentororder.sql
new file mode 100644
index 00000000..ea98da1b
--- /dev/null
+++ b/fixtures/db_views/view_coderdojochi_mentororder.sql
@@ -0,0 +1,23 @@
+SELECT
+ m.mentor_id,
+ m.user_id,
+ m.mentor_name,
+ m.background_check,
+ mo.check_in,
+ mo.created_at AS sign_up_time,
+ mo.session_id,
+ s.code,
+ s.title,
+ s.start_date,
+ s.end_date,
+ date_part('hour' :: text, (s.end_date - s.start_date)) AS class_hours
+FROM
+ (
+ (
+ coderdojochi_mentororder mo
+ JOIN view_weallcode_mentor m ON ((mo.mentor_id = m.mentor_id))
+ )
+ JOIN view_coderdojochi_session s ON ((mo.session_id = s.id))
+ )
+WHERE
+ (mo.is_active = true);
diff --git a/fixtures/db_views/view_coderdojochi_order.sql b/fixtures/db_views/view_coderdojochi_order.sql
new file mode 100644
index 00000000..23f91657
--- /dev/null
+++ b/fixtures/db_views/view_coderdojochi_order.sql
@@ -0,0 +1,37 @@
+SELECT
+ o.id,
+ o.session_id,
+ o.student_id,
+ o.is_active,
+ o.check_in,
+ o.created_at,
+ s.first_name,
+ s.last_name,
+ s.guardian_zip,
+ s.age,
+ s.gender,
+ s.school_name,
+ s.school_type,
+ s.eth_american_indian,
+ s.eth_asian,
+ s.eth_arab,
+ s.eth_black_african_american,
+ s.eth_hispanic_latino,
+ s.eth_pacific_islander,
+ s.eth_white,
+ s.eth_not_list,
+ ses.code,
+ ses.title,
+ ses.start_date,
+ ses.end_date,
+ ses.name
+FROM
+ (
+ (
+ coderdojochi_order o
+ LEFT JOIN view_coderdojochi_session ses ON ((o.session_id = ses.id))
+ )
+ LEFT JOIN view_coderdojochi_student_w_eth s ON ((o.student_id = s.id))
+ )
+WHERE
+ (o.is_active = true);
diff --git a/fixtures/db_views/view_coderdojochi_session.sql b/fixtures/db_views/view_coderdojochi_session.sql
new file mode 100644
index 00000000..9905dbe6
--- /dev/null
+++ b/fixtures/db_views/view_coderdojochi_session.sql
@@ -0,0 +1,26 @@
+SELECT
+ ses.id,
+ ses.course_id,
+ c.code,
+ c.title,
+ ses.start_date,
+ ses.old_end_date AS end_date,
+ l.name,
+ l.address,
+ l.zip,
+ ses.capacity,
+ ses.instructor_id AS teacher_id,
+ ses.is_active,
+ ses.created_at,
+ ses.mentor_capacity,
+ ses.gender_limitation
+FROM
+ (
+ (
+ coderdojochi_session ses
+ LEFT JOIN coderdojochi_course c ON ((ses.course_id = c.id))
+ )
+ LEFT JOIN coderdojochi_location l ON ((ses.location_id = l.id))
+ )
+WHERE
+ (ses.is_active = true);
diff --git a/fixtures/db_views/view_coderdojochi_session_w_signups.sql b/fixtures/db_views/view_coderdojochi_session_w_signups.sql
new file mode 100644
index 00000000..2f3ed3c3
--- /dev/null
+++ b/fixtures/db_views/view_coderdojochi_session_w_signups.sql
@@ -0,0 +1,62 @@
+SELECT
+ s.id,
+ s.course_id,
+ s.code,
+ s.title,
+ s.start_date,
+ s.end_date,
+ s.name,
+ s.address,
+ s.zip,
+ s.capacity,
+ s.teacher_id,
+ s.is_active,
+ s.created_at,
+ s.mentor_capacity,
+ s.gender_limitation,
+ COALESCE(so.student_signup, (0) :: bigint) AS student_signup,
+ COALESCE(so.student_attend, (0) :: bigint) AS student_attend,
+ COALESCE(mo.mentor_signup, (0) :: bigint) AS mentor_signup,
+ COALESCE(mo.mentor_attend, (0) :: bigint) AS mentor_attend
+FROM
+ (
+ (
+ view_coderdojochi_session s
+ LEFT JOIN (
+ SELECT
+ coderdojochi_order.session_id,
+ count(coderdojochi_order.student_id) AS student_signup,
+ sum(
+ CASE
+ WHEN (coderdojochi_order.check_in IS NOT NULL) THEN 1
+ ELSE 0
+ END
+ ) AS student_attend
+ FROM
+ coderdojochi_order
+ GROUP BY
+ coderdojochi_order.session_id
+ ORDER BY
+ coderdojochi_order.session_id
+ ) so ON ((s.id = so.session_id))
+ )
+ LEFT JOIN (
+ SELECT
+ coderdojochi_mentororder.session_id,
+ count(coderdojochi_mentororder.mentor_id) AS mentor_signup,
+ sum(
+ CASE
+ WHEN (coderdojochi_mentororder.check_in IS NOT NULL) THEN 1
+ ELSE 0
+ END
+ ) AS mentor_attend
+ FROM
+ coderdojochi_mentororder
+ GROUP BY
+ coderdojochi_mentororder.session_id
+ ORDER BY
+ coderdojochi_mentororder.session_id
+ ) mo ON ((s.id = mo.session_id))
+ )
+ORDER BY
+ s.start_date;
diff --git a/fixtures/db_views/view_coderdojochi_student.sql b/fixtures/db_views/view_coderdojochi_student.sql
new file mode 100644
index 00000000..fc814fcf
--- /dev/null
+++ b/fixtures/db_views/view_coderdojochi_student.sql
@@ -0,0 +1,55 @@
+SELECT
+ max(s.id) AS id,
+ s.guardian_id,
+ g.zip AS guardian_zip,
+ s.first_name,
+ s.last_name,
+ s.birthday,
+ (
+ date_part(
+ 'day' :: text,
+ (
+ (CURRENT_DATE) :: timestamp with time zone - s.birthday
+ )
+ ) / (365) :: double precision
+ ) AS age,
+ COALESCE(dq.mapping, 'unknown' :: character varying) AS gender,
+ max(s.created_at) AS created_at,
+ max(s.updated_at) AS updated_at,
+ s.is_active,
+ s.school_name,
+ s.school_type
+FROM
+ (
+ (
+ coderdojochi_student s
+ LEFT JOIN (
+ SELECT
+ coderdojochi_guardian.id,
+ coderdojochi_guardian.user_id,
+ coderdojochi_guardian.is_active,
+ coderdojochi_guardian.phone,
+ coderdojochi_guardian.created_at,
+ coderdojochi_guardian.updated_at,
+ coderdojochi_guardian.zip
+ FROM
+ coderdojochi_guardian
+ WHERE
+ (coderdojochi_guardian.is_active = true)
+ ) g ON ((s.guardian_id = g.id))
+ )
+ LEFT JOIN dq_lookup_gender dq ON (((dq.value) :: text = (s.gender) :: text))
+ )
+WHERE
+ (s.is_active = true)
+GROUP BY
+ s.guardian_id,
+ g.zip,
+ s.first_name,
+ s.last_name,
+ s.birthday,
+ s.gender,
+ s.is_active,
+ s.school_name,
+ s.school_type,
+ dq.mapping;
diff --git a/fixtures/db_views/view_coderdojochi_student_w_eth.sql b/fixtures/db_views/view_coderdojochi_student_w_eth.sql
new file mode 100644
index 00000000..3a8994aa
--- /dev/null
+++ b/fixtures/db_views/view_coderdojochi_student_w_eth.sql
@@ -0,0 +1,33 @@
+SELECT
+ s.id,
+ s.guardian_id,
+ s.guardian_zip,
+ s.first_name,
+ s.last_name,
+ s.birthday,
+ s.age,
+ s.gender,
+ s.created_at,
+ s.updated_at,
+ s.is_active,
+ s.school_name,
+ s.school_type,
+ CASE
+ WHEN (sr.student_id IS NULL) THEN 'false' :: text
+ ELSE 'true' :: text
+ END AS ethnicity_entered,
+ COALESCE(sr.eth_american_indian, (0) :: bigint) AS eth_american_indian,
+ COALESCE(sr.eth_asian, (0) :: bigint) AS eth_asian,
+ COALESCE(sr.eth_arab, (0) :: bigint) AS eth_arab,
+ COALESCE(sr.eth_black_african_american, (0) :: bigint) AS eth_black_african_american,
+ COALESCE(sr.eth_hispanic_latino, (0) :: bigint) AS eth_hispanic_latino,
+ COALESCE(sr.eth_pacific_islander, (0) :: bigint) AS eth_pacific_islander,
+ COALESCE(sr.eth_white, (0) :: bigint) AS eth_white,
+ COALESCE(sr.eth_not_list, (0) :: bigint) AS eth_not_list
+FROM
+ (
+ view_coderdojochi_student s
+ LEFT JOIN view_student_race_ethnicity_unpivot sr ON ((s.id = sr.student_id))
+ )
+ORDER BY
+ s.id;
diff --git a/fixtures/db_views/view_school_names.sql b/fixtures/db_views/view_school_names.sql
new file mode 100644
index 00000000..dfddf48d
--- /dev/null
+++ b/fixtures/db_views/view_school_names.sql
@@ -0,0 +1,9 @@
+SELECT
+ DISTINCT coderdojochi_student.school_name,
+ count(*) AS count
+FROM
+ coderdojochi_student
+WHERE
+ (coderdojochi_student.is_active = true)
+GROUP BY
+ coderdojochi_student.school_name;
diff --git a/fixtures/db_views/view_student_race_ethnicity_unpivot.sql b/fixtures/db_views/view_student_race_ethnicity_unpivot.sql
new file mode 100644
index 00000000..6517008b
--- /dev/null
+++ b/fixtures/db_views/view_student_race_ethnicity_unpivot.sql
@@ -0,0 +1,57 @@
+SELECT
+ sr.student_id,
+ sum(
+ CASE
+ WHEN (r.id = 1) THEN 1
+ ELSE 0
+ END
+ ) AS eth_american_indian,
+ sum(
+ CASE
+ WHEN (r.id = 2) THEN 1
+ ELSE 0
+ END
+ ) AS eth_asian,
+ sum(
+ CASE
+ WHEN (r.id = 3) THEN 1
+ ELSE 0
+ END
+ ) AS eth_arab,
+ sum(
+ CASE
+ WHEN (r.id = 4) THEN 1
+ ELSE 0
+ END
+ ) AS eth_black_african_american,
+ sum(
+ CASE
+ WHEN (r.id = 5) THEN 1
+ ELSE 0
+ END
+ ) AS eth_hispanic_latino,
+ sum(
+ CASE
+ WHEN (r.id = 6) THEN 1
+ ELSE 0
+ END
+ ) AS eth_pacific_islander,
+ sum(
+ CASE
+ WHEN (r.id = 7) THEN 1
+ ELSE 0
+ END
+ ) AS eth_white,
+ sum(
+ CASE
+ WHEN (r.id = 8) THEN 1
+ ELSE 0
+ END
+ ) AS eth_not_list
+FROM
+ (
+ coderdojochi_student_race_ethnicity sr
+ LEFT JOIN coderdojochi_raceethnicity r ON ((sr.raceethnicity_id = r.id))
+ )
+GROUP BY
+ sr.student_id;
diff --git a/fixtures/db_views/view_weallcode_mentor.sql b/fixtures/db_views/view_weallcode_mentor.sql
new file mode 100644
index 00000000..fbe4f944
--- /dev/null
+++ b/fixtures/db_views/view_weallcode_mentor.sql
@@ -0,0 +1,92 @@
+SELECT
+ m.id AS mentor_id,
+ u.id AS user_id,
+ (
+ ((u.first_name) :: text || ' ' :: text) || (u.last_name) :: text
+ ) AS mentor_name,
+ m.is_active,
+ m.created_at,
+ date_trunc('month' :: text, m.created_at) AS created_month,
+ m.updated_at,
+ CASE
+ WHEN (m.background_check IS TRUE) THEN 1
+ ELSE 0
+ END AS background_check,
+ max(s.start_date) AS last_class_date,
+ min(s.start_date) AS first_class_date,
+ sum(
+ CASE
+ WHEN (s.start_date IS NOT NULL) THEN 1
+ ELSE 0
+ END
+ ) AS num_classes,
+ COALESCE(
+ sum(
+ date_part('hour' :: text, (s.end_date - s.start_date))
+ ),
+ (0) :: double precision
+ ) AS hours_volunteered,
+ date_part(
+ 'days' :: text,
+ (
+ (CURRENT_DATE) :: timestamp with time zone - max(s.start_date)
+ )
+ ) AS days_since_last_class,
+ CASE
+ WHEN (
+ date_part(
+ 'days' :: text,
+ (
+ (CURRENT_DATE) :: timestamp with time zone - max(s.start_date)
+ )
+ ) <= (365) :: double precision
+ ) THEN 1
+ ELSE 0
+ END AS active_mentor,
+ CASE
+ WHEN (
+ sum(
+ CASE
+ WHEN (s.start_date IS NOT NULL) THEN 1
+ ELSE 0
+ END
+ ) > 0
+ ) THEN 1
+ ELSE 0
+ END AS mentored
+FROM
+ (
+ (
+ (
+ coderdojochi_mentor m
+ LEFT JOIN coderdojochi_cdcuser u ON ((m.user_id = u.id))
+ )
+ LEFT JOIN coderdojochi_mentororder mo ON (
+ (
+ (mo.mentor_id = m.id)
+ AND (mo.is_active = true)
+ )
+ )
+ )
+ LEFT JOIN view_coderdojochi_session s ON (
+ (
+ (mo.session_id = s.id)
+ AND (s.is_active = true)
+ )
+ )
+ )
+WHERE
+ (m.is_active = true)
+GROUP BY
+ m.id,
+ m.user_id,
+ u.id,
+ (
+ ((u.first_name) :: text || ' ' :: text) || (u.last_name) :: text
+ ),
+ m.is_active,
+ m.created_at,
+ m.updated_at,
+ m.background_check
+ORDER BY
+ m.id;
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..171bd3f5
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,32 @@
+[tool.isort]
+line_length = 120
+multi_line_output = 3
+include_trailing_comma = true
+#default_section = "THIRDPARTY"
+#known_first_party = []
+#known_third_party = []
+skip=['.eggs', '.git', '.hg', '.mypy_cache', '.nox', '.pants.d', '.tox', '.venv', '_build', 'buck-out', 'build', 'dist', 'node_modules', 'venv', 'migrations']
+
+[tool.black]
+line-length = 120
+#target-version = ['py38']
+#include = '\.pyi?$'
+exclude = '''
+
+(
+ /(
+ \.eggs # exclude a few common directories in the
+ | \.git # root of the project
+ | \.hg
+ | \.mypy_cache
+ | \.tox
+ | \.venv
+ | _build
+ | buck-out
+ | build
+ | dist
+ | migrations
+ )/
+
+)
+'''
diff --git a/tasks.py b/tasks.py
index 0483b9ab..f7dddbfc 100644
--- a/tasks.py
+++ b/tasks.py
@@ -1,5 +1,3 @@
-import os
-
import environ
from invoke import task
@@ -8,39 +6,44 @@
@task
def release(ctx):
+ if env.bool("DEBUG", default=False):
+ format(ctx)
+
collect_static(ctx)
migrate(ctx)
load_fixtures(ctx)
-@task(help={'port': 'Port to use when serving traffic. Defaults to $PORT.'})
-def start(ctx, port=env.int('PORT', default=8000)):
- ctx.run(f'gunicorn coderdojochi.wsgi -w 2 -b 0.0.0.0:{port} --reload --log-file -')
+@task(help={"port": "Port to use when serving traffic. Defaults to $PORT."})
+def start(ctx, port=env.int("PORT", default=8000)):
+ ctx.run(f"gunicorn coderdojochi.wsgi -w 2 -b 0.0.0.0:{port} --reload --log-file -")
@task
def migrate(ctx):
- ctx.run('python3 manage.py migrate')
+ ctx.run("python3 manage.py migrate")
@task
def load_fixtures(ctx):
- if env.bool('ENABLE_DEV_FIXTURES', default=False):
- ctx.run('python3 manage.py loaddata fixtures/*.json')
+ if env.bool("ENABLE_DEV_FIXTURES", default=False):
+ ctx.run("python3 manage.py loaddata fixtures/*.json")
@task
def collect_static(ctx):
- if not env.bool('DEBUG', default=False):
- ctx.run('python3 manage.py collectstatic --no-input')
+ if not env.bool("DEBUG", default=False):
+ ctx.run("python3 manage.py collectstatic --no-input")
-@task(help={'app': 'Specific app to run tests on. Defaults to all apps.'})
-def test(ctx, app=''):
- ctx.run(f'python3 manage.py test {app}')
+@task(help={"app": "Specific app to run tests on. Defaults to all apps."})
+def test(ctx, app=""):
+ ctx.run(f"python3 manage.py test {app}")
format(ctx)
@task
def format(ctx):
- ctx.run('autopep8 -iaarj4 --exclude="**/migrations/*" --max-line-length="120" .')
+ ctx.run("pip install -qU black isort")
+ ctx.run("isort -m 3 --trailing-comma .")
+ ctx.run("black .")
diff --git a/weallcode/__init__.py b/weallcode/__init__.py
index 812c5aa7..6ea20755 100644
--- a/weallcode/__init__.py
+++ b/weallcode/__init__.py
@@ -1 +1 @@
-default_app_config = 'weallcode.apps.WeAllCodeConfig'
+default_app_config = "weallcode.apps.WeAllCodeConfig"
diff --git a/weallcode/admin.py b/weallcode/admin.py
index b8042bae..cc2a64ea 100644
--- a/weallcode/admin.py
+++ b/weallcode/admin.py
@@ -8,44 +8,47 @@
class StaffMemberAdmin(admin.ModelAdmin):
def member_image(self, obj):
return format_html(f' ')
+
member_image.allow_tags = True
list_display = [
- 'member_image',
- 'name',
- 'role',
- 'is_active',
+ "member_image",
+ "name",
+ "role",
+ "is_active",
]
list_filter = [
- 'is_active',
- 'role',
+ "is_active",
+ "role",
]
readonly_fields = [
- 'member_image',
- 'created_at',
- 'updated_at',
+ "member_image",
+ "created_at",
+ "updated_at",
]
- ordering = [
- 'name'
- ]
+ ordering = ["name"]
fieldsets = (
- (None, {
- 'fields': (
- 'name',
- 'role',
- 'description',
- 'linkedin',
- 'image',
- 'member_image',
- 'is_active',
- 'created_at',
- 'updated_at',
- ),
- }),
+ (
+ None,
+ {
+ "fields": (
+ "name",
+ "role",
+ "join_date",
+ "description",
+ "linkedin",
+ "image",
+ "member_image",
+ "is_active",
+ "created_at",
+ "updated_at",
+ ),
+ },
+ ),
)
@@ -53,88 +56,94 @@ def member_image(self, obj):
class BoardMemberAdmin(admin.ModelAdmin):
def member_image(self, obj):
return format_html(f' ')
+
member_image.allow_tags = True
list_display = [
- 'member_image',
- 'name',
- 'role',
- 'is_active',
+ "member_image",
+ "name",
+ "role",
+ "is_active",
]
list_filter = [
- 'is_active',
- 'role',
+ "is_active",
+ "role",
]
readonly_fields = [
- 'member_image',
- 'created_at',
- 'updated_at',
+ "member_image",
+ "created_at",
+ "updated_at",
]
- ordering = [
- 'name'
- ]
+ ordering = ["name"]
fieldsets = (
- (None, {
- 'fields': (
- 'name',
- 'role',
- 'description',
- 'linkedin',
- 'image',
- 'member_image',
- 'is_active',
- 'created_at',
- 'updated_at',
- ),
- }),
+ (
+ None,
+ {
+ "fields": (
+ "name",
+ "role",
+ "join_date",
+ "description",
+ "linkedin",
+ "image",
+ "member_image",
+ "is_active",
+ "created_at",
+ "updated_at",
+ ),
+ },
+ ),
)
@admin.register(AssociateBoardMember)
class AssociateBoardMemberAdmin(admin.ModelAdmin):
-
def member_image(self, obj):
return format_html(f' ')
+
member_image.allow_tags = True
list_display = [
- 'member_image',
- 'name',
- 'role',
- 'is_active',
+ "member_image",
+ "name",
+ "role",
+ "join_date",
+ "is_active",
]
list_filter = [
- 'is_active',
- 'role',
+ "is_active",
+ "role",
]
readonly_fields = [
- 'member_image',
- 'created_at',
- 'updated_at',
+ "member_image",
+ "created_at",
+ "updated_at",
]
- ordering = [
- 'name'
- ]
+ ordering = ["name"]
fieldsets = (
- (None, {
- 'fields': (
- 'name',
- 'role',
- 'description',
- 'linkedin',
- 'image',
- 'member_image',
- 'is_active',
- 'created_at',
- 'updated_at',
- ),
- }),
+ (
+ None,
+ {
+ "fields": (
+ "name",
+ "role",
+ "join_date",
+ "description",
+ "linkedin",
+ "image",
+ "member_image",
+ "is_active",
+ "created_at",
+ "updated_at",
+ ),
+ },
+ ),
)
diff --git a/weallcode/apps.py b/weallcode/apps.py
index 77a38dca..0c3b8aa7 100644
--- a/weallcode/apps.py
+++ b/weallcode/apps.py
@@ -1,7 +1,6 @@
from django.apps import AppConfig
-from django.utils.translation import gettext_lazy as _
class WeAllCodeConfig(AppConfig):
- name = 'weallcode'
- verbose_name = _("We All Code")
+ name = "weallcode"
+ verbose_name = "We All Code"
diff --git a/weallcode/forms.py b/weallcode/forms.py
index 0893e1f4..46e01dbf 100644
--- a/weallcode/forms.py
+++ b/weallcode/forms.py
@@ -9,12 +9,12 @@
class ContactForm(forms.Form):
widths = (
- ('name', 'small-6'),
- ('email', 'small-6'),
- ('interest', 'small-6'),
- ('phone', 'small-6'),
- ('message', 'small-12'),
- ('captcha', ''),
+ ("name", "small-6"),
+ ("email", "small-6"),
+ ("interest", "small-6"),
+ ("phone", "small-6"),
+ ("message", "small-12"),
+ ("captcha", ""),
)
captcha = ReCaptchaField(
@@ -23,62 +23,62 @@ class ContactForm(forms.Form):
name = forms.CharField(
max_length=100,
- label='Full Name',
+ label="Full Name",
)
email = forms.EmailField(
max_length=200,
- label='Email Address',
+ label="Email Address",
widget=forms.TextInput(
attrs={
- 'type': 'email',
- 'placeholder': 'email@example.com',
+ "type": "email",
+ "placeholder": "email@example.com",
},
),
)
interest = forms.ChoiceField(
choices=[
- ('volunteer', 'Volunteer'),
- ('donate', 'Donate'),
- ('sponsor', 'Sponsor'),
- ('collaborate', 'Collaborate'),
- ('other', 'Other'),
+ ("volunteer", "Volunteer"),
+ ("donate", "Donate"),
+ ("sponsor", "Sponsor"),
+ ("collaborate", "Collaborate"),
+ ("other", "Other"),
],
- label='Topic of Interest',
+ label="Topic of Interest",
)
phone = forms.CharField(
max_length=20,
- label='Phone Number',
+ label="Phone Number",
widget=forms.TextInput(
attrs={
- 'type': 'tel',
- 'placeholder': '+1 555 555-5555',
- 'minlength': '10',
- 'maxlength': '20',
+ "type": "tel",
+ "placeholder": "+1 555 555-5555",
+ "minlength": "10",
+ "maxlength": "20",
},
),
)
message = forms.CharField(
- label='Message',
+ label="Message",
widget=forms.Textarea(
attrs={
- 'placeholder': 'Enter your message',
- 'minlength': 25,
- 'maxlength': 500,
+ "placeholder": "Enter your message",
+ "minlength": 25,
+ "maxlength": 500,
},
),
)
def as_grid(self):
- return ''.join([self.field_html(f[0], f[1]) for f in self.widths])
+ return "".join([self.field_html(f[0], f[1]) for f in self.widths])
def field_html(self, field_name, field_classes):
field = self[field_name]
- if field_classes == '':
+ if field_classes == "":
return f"{field}{field.errors}"
return f"{field.label_tag()}{field}{field.errors}
"
@@ -90,10 +90,10 @@ def send_email(self):
subject=f"{data['name']} | We All Code Contact Form",
recipients=[settings.CONTACT_EMAIL],
reply_to=[f"{data['name']}<{data['email']}>"],
- template_name='contact-email',
+ template_name="contact_email",
merge_global_data={
- 'interest': data['interest'],
- 'message': data['message'],
- 'phone': data['phone'],
+ "interest": data["interest"],
+ "message": data["message"],
+ "phone": data["phone"],
},
)
diff --git a/weallcode/migrations/0007_auto_20201229_1542.py b/weallcode/migrations/0007_auto_20201229_1542.py
new file mode 100644
index 00000000..15e99992
--- /dev/null
+++ b/weallcode/migrations/0007_auto_20201229_1542.py
@@ -0,0 +1,44 @@
+# Generated by Django 3.1.2 on 2020-12-29 21:42
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('weallcode', '0006_auto_20200615_1451'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='associateboardmember',
+ options={},
+ ),
+ migrations.AlterModelOptions(
+ name='boardmember',
+ options={},
+ ),
+ migrations.AlterModelOptions(
+ name='staffmember',
+ options={},
+ ),
+ migrations.AddField(
+ model_name='associateboardmember',
+ name='join_date',
+ field=models.DateField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Join Date'),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='boardmember',
+ name='join_date',
+ field=models.DateField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Join Date'),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='staffmember',
+ name='join_date',
+ field=models.DateField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Join Date'),
+ preserve_default=False,
+ ),
+ ]
diff --git a/weallcode/models.py b/weallcode/models.py
deleted file mode 100644
index 1f9dc7f5..00000000
--- a/weallcode/models.py
+++ /dev/null
@@ -1,118 +0,0 @@
-from django.db import models
-from django.utils.translation import gettext_lazy as _
-
-CHAIR = 'Chair'
-VICE_CHAIR = 'Vice Chair'
-SECRETARY = 'Secretary'
-TREASURER = 'Treasurer'
-DIRECTOR = 'Director'
-PRESIDENT = 'President'
-
-ROLE_CHOICES = [
- (CHAIR, 'Chair'),
- (VICE_CHAIR, 'Vice Chair'),
- (SECRETARY, 'Secretary'),
- (TREASURER, 'Treasurer'),
- (DIRECTOR, 'Director'),
- (PRESIDENT, 'President'),
-]
-
-
-class CommonInfo(models.Model):
-
- name = models.CharField(
- max_length=255,
- )
-
- role = models.CharField(
- choices=ROLE_CHOICES,
- max_length=255,
- default=DIRECTOR,
- )
-
- image = models.ImageField(
- upload_to='staff/',
- blank=True,
- null=True,
- )
-
- description = models.CharField(
- max_length=255,
- blank=True,
- null=True,
- )
-
- linkedin = models.URLField(
- max_length=200,
- blank=True,
- null=True,
- )
-
- # Active
- is_active = models.BooleanField(
- default=True,
- )
-
- # Auto create/update
- created_at = models.DateTimeField(
- auto_now_add=True,
- )
-
- updated_at = models.DateTimeField(
- auto_now=True,
- )
-
- class Meta:
- abstract = True
-
-
-class StaffMember(CommonInfo):
-
- role = models.CharField(
- max_length=255,
- )
-
- image = models.ImageField(
- upload_to='staff/',
- blank=True,
- null=True,
- )
-
- class Meta:
- verbose_name = _("Staff Member")
- verbose_name_plural = _("Staff Members")
-
- def __str__(self):
- return self.name
-
-
-class BoardMember(CommonInfo):
-
- image = models.ImageField(
- upload_to='board/',
- blank=True,
- null=True,
- )
-
- class Meta:
- verbose_name = _("Board Member")
- verbose_name_plural = _("Board Members")
-
- def __str__(self):
- return self.name
-
-
-class AssociateBoardMember(CommonInfo):
-
- image = models.ImageField(
- upload_to='associate-board/',
- blank=True,
- null=True,
- )
-
- class Meta:
- verbose_name = _("Associate Board Member")
- verbose_name_plural = _("Associate Board Members")
-
- def __str__(self):
- return self.name
diff --git a/weallcode/models/__init__.py b/weallcode/models/__init__.py
new file mode 100644
index 00000000..dca98aa4
--- /dev/null
+++ b/weallcode/models/__init__.py
@@ -0,0 +1,4 @@
+from .associate_board_member import *
+from .board_member import *
+from .common import *
+from .staff import *
diff --git a/weallcode/models/associate_board_member.py b/weallcode/models/associate_board_member.py
new file mode 100644
index 00000000..70fbd311
--- /dev/null
+++ b/weallcode/models/associate_board_member.py
@@ -0,0 +1,20 @@
+from collections import defaultdict
+from itertools import chain
+
+from django.db import models
+
+from .common import CommonBoardMemberManager, CommonInfo
+
+
+class AssociateBoardMember(CommonInfo):
+
+ image = models.ImageField(
+ upload_to="associate-board/",
+ blank=True,
+ null=True,
+ )
+
+ objects = CommonBoardMemberManager()
+
+ def __str__(self):
+ return self.name
diff --git a/weallcode/models/board_member.py b/weallcode/models/board_member.py
new file mode 100644
index 00000000..c4761487
--- /dev/null
+++ b/weallcode/models/board_member.py
@@ -0,0 +1,17 @@
+from django.db import models
+
+from .common import CommonBoardMemberManager, CommonInfo
+
+
+class BoardMember(CommonInfo):
+
+ image = models.ImageField(
+ upload_to="board/",
+ blank=True,
+ null=True,
+ )
+
+ objects = CommonBoardMemberManager()
+
+ def __str__(self):
+ return self.name
diff --git a/weallcode/models/common.py b/weallcode/models/common.py
new file mode 100644
index 00000000..a6599e18
--- /dev/null
+++ b/weallcode/models/common.py
@@ -0,0 +1,74 @@
+from collections import defaultdict
+from itertools import chain
+
+from django.db import models
+from django.db.models import Case, When
+
+CHAIR = "Chair"
+VICE_CHAIR = "Vice Chair"
+SECRETARY = "Secretary"
+TREASURER = "Treasurer"
+DIRECTOR = "Director"
+PRESIDENT = "President"
+
+ROLE_CHOICES = [
+ (CHAIR, "Chair"),
+ (VICE_CHAIR, "Vice Chair"),
+ (SECRETARY, "Secretary"),
+ (TREASURER, "Treasurer"),
+ (DIRECTOR, "Director"),
+ (PRESIDENT, "President"),
+]
+
+
+class CommonInfo(models.Model):
+ name = models.CharField(
+ max_length=255,
+ )
+ role = models.CharField(
+ choices=ROLE_CHOICES,
+ max_length=255,
+ default=DIRECTOR,
+ )
+ image = models.ImageField(
+ upload_to="staff/",
+ blank=True,
+ null=True,
+ )
+ description = models.CharField(
+ max_length=255,
+ blank=True,
+ null=True,
+ )
+ linkedin = models.URLField(
+ max_length=200,
+ blank=True,
+ null=True,
+ )
+ join_date = models.DateField("Join Date", auto_now_add=True)
+ is_active = models.BooleanField(
+ default=True,
+ )
+ created_at = models.DateTimeField(
+ auto_now_add=True,
+ )
+ updated_at = models.DateTimeField(
+ auto_now=True,
+ )
+
+ class Meta:
+ abstract = True
+
+
+class CommonBoardMemberManager(models.Manager):
+ def get_queryset(self):
+ return super().get_queryset().filter(is_active=True)
+
+ def get_sorted(self):
+ """
+ Reordering by role.
+ """
+
+ roles = [CHAIR, VICE_CHAIR, TREASURER, SECRETARY, DIRECTOR]
+ order = Case(*[When(role=role, then=pos) for pos, role in enumerate(roles)])
+ return self.get_queryset().filter(role__in=roles).order_by(order, "join_date", "name")
diff --git a/weallcode/models/staff.py b/weallcode/models/staff.py
new file mode 100644
index 00000000..9aedef43
--- /dev/null
+++ b/weallcode/models/staff.py
@@ -0,0 +1,26 @@
+from django.db import models
+
+from .common import CommonInfo
+
+
+class StaffMemberManager(models.Manager):
+ def get_queryset(self):
+ return super().get_queryset().filter(is_active=True)
+
+
+class StaffMember(CommonInfo):
+
+ role = models.CharField(
+ max_length=255,
+ )
+
+ image = models.ImageField(
+ upload_to="staff/",
+ blank=True,
+ null=True,
+ )
+
+ objects = StaffMemberManager()
+
+ def __str__(self):
+ return self.name
diff --git a/weallcode/templates/weallcode/_base.html b/weallcode/templates/weallcode/_base.html
index ca0dbaab..59c7bad0 100644
--- a/weallcode/templates/weallcode/_base.html
+++ b/weallcode/templates/weallcode/_base.html
@@ -89,6 +89,7 @@
ga('create', 'UA-78618586-1', 'auto');
ga('set', 'transport', 'beacon');
ga('send', 'pageview');
+