diff --git a/.flake8 b/.flake8
index 98763170..0583375e 100644
--- a/.flake8
+++ b/.flake8
@@ -7,4 +7,6 @@ select = B950
extend-ignore = E203, E501
per-file-ignores =
*/__init__.py:F401
+ */migrations/*:B950,
+ */puput_migrations/*:B950,
indymeet/settings/*.py:F403,F405
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index a41fe5e8..0c9dce9c 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -1,3 +1,9 @@
# .git-blame-ignore-revs
# Apply pre-commit to the project
b9f6c18a38a38728e27889762300d6a30821bfaa
+# Run pre-commit on all files
+# + 3x subsequent, manual flake8 B950 fixes
+a945aa754469fff23731ad58b27ca6644d5dfe22
+144cfa12ad8947fe1c2de75a3cd06bb9cca325f0
+5af48fc6cf958c416400b51fc03f331f21ee192c
+d8db7d19915ca88b8b427fab8b928af29229fd07
diff --git a/.github/workflows/develop_djangonaut_space.yml b/.github/workflows/develop_djangonaut_space.yml
index eff2169c..c7777f01 100644
--- a/.github/workflows/develop_djangonaut_space.yml
+++ b/.github/workflows/develop_djangonaut_space.yml
@@ -35,9 +35,6 @@ jobs:
env:
ENVIRONMENT: 'production'
DJANGO_SETTINGS_MODULE: 'indymeet.settings.production'
- APPINSIGHTS_INSTRUMENTATIONKEY: ${{ secrets.STAGING_APPINSIGHTS_INSTRUMENTATIONKEY }}
- APPLICATIONINSIGHTSAGENT_EXTENSION_ENABLED: 'true'
- DEBUG: 'True'
RECAPTCHA_PRIVATE_KEY: ${{ secrets.RECAPTCHA_PRIVATE_KEY }}
RECAPTCHA_PUBLIC_KEY: ${{ secrets.RECAPTCHA_PUBLIC_KEY }}
SCM_DO_BUILD_DURING_DEPLOYMENT: 'true'
diff --git a/.github/workflows/main_djangonaut-space.yml b/.github/workflows/main_djangonaut-space.yml
index ee48a40b..35e9bcb0 100644
--- a/.github/workflows/main_djangonaut-space.yml
+++ b/.github/workflows/main_djangonaut-space.yml
@@ -21,9 +21,6 @@ jobs:
env:
ENVIRONMENT: 'production'
DJANGO_SETTINGS_MODULE: 'indymeet.settings.production'
- APPINSIGHTS_INSTRUMENTATIONKEY: ${{ secrets.APPINSIGHTS_INSTRUMENTATIONKEY }}
- APPLICATIONINSIGHTSAGENT_EXTENSION_ENABLED: 'true'
- DEBUG: False
RECAPTCHA_PRIVATE_KEY: ${{ secrets.RECAPTCHA_PRIVATE_KEY }}
RECAPTCHA_PUBLIC_KEY: ${{ secrets.RECAPTCHA_PUBLIC_KEY }}
SCM_DO_BUILD_DURING_DEPLOYMENT: 'true'
diff --git a/README.md b/README.md
index b61f9006..ec113167 100644
--- a/README.md
+++ b/README.md
@@ -134,7 +134,8 @@ This is an example of how to list things you need to use the software and how to
```
```sh
postgres=# CREATE DATABASE "djangonaut-space";
- CREATE DATABASE
+ postgres=# CREATE USER djangonaut WITH SUPERUSER PASSWORD 'djangonaut';
+ postgres=# GRANT ALL PRIVILEGES ON DATABASE 'djangonaut-space' TO djangonaut;
```
```sh
postgres=# exit
diff --git a/accounts/factories.py b/accounts/factories.py
index c99a67ea..b22f8d4c 100644
--- a/accounts/factories.py
+++ b/accounts/factories.py
@@ -1,6 +1,10 @@
+from __future__ import annotations
+
import factory
from django.db.models.signals import post_save
-from accounts.models import CustomUser, UserProfile
+
+from accounts.models import CustomUser
+from accounts.models import UserProfile
@factory.django.mute_signals(post_save)
diff --git a/accounts/forms.py b/accounts/forms.py
index f5e4f6b8..ecc65b55 100644
--- a/accounts/forms.py
+++ b/accounts/forms.py
@@ -1,21 +1,14 @@
from __future__ import annotations
-from captcha.fields import ReCaptchaField
-from captcha.widgets import ReCaptchaV2Checkbox
from django import forms
-from django.contrib.auth.forms import UserChangeForm
from django.contrib.auth.forms import UserCreationForm
+from django_recaptcha.fields import ReCaptchaField
+from django_recaptcha.widgets import ReCaptchaV2Checkbox
from .models import CustomUser
-class CustomUserCreationForm(UserCreationForm):
- captcha = ReCaptchaField(widget=ReCaptchaV2Checkbox())
- email_consent = forms.BooleanField(
- help_text="Required: Please check this to consent to receiving "
- "administrative emails like: email verification, password reset etc.",
- label="Email Consent*",
- )
+class BaseCustomUserForm(forms.ModelForm):
receive_newsletter = forms.BooleanField(
required=False,
help_text="Optional: Please check this to opt-in for receiving "
@@ -35,6 +28,15 @@ class CustomUserCreationForm(UserCreationForm):
"emails about upcoming program sessions. You can opt-out on "
"your profile page at anytime.",
)
+
+
+class CustomUserCreationForm(BaseCustomUserForm, UserCreationForm):
+ captcha = ReCaptchaField(widget=ReCaptchaV2Checkbox())
+ email_consent = forms.BooleanField(
+ help_text="Required: Please check this to consent to receiving "
+ "administrative emails like: email verification, password reset etc.",
+ label="Email Consent*",
+ )
accepted_coc = forms.BooleanField(
required=True,
label="Accept CoC*",
@@ -65,12 +67,29 @@ def __init__(self, *args, **kwargs):
self.fields["email"].required = True
-class CustomUserChangeForm(UserChangeForm):
- class Meta:
+class CustomUserChangeForm(BaseCustomUserForm):
+ class Meta(BaseCustomUserForm):
model = CustomUser
fields = (
"username",
"email",
"first_name",
"last_name",
+ "receive_program_updates",
+ "receive_event_updates",
+ "receive_newsletter",
)
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.fields["email"].required = True
+ user = kwargs["instance"]
+ if user.profile.email_confirmed:
+ help_text = (
+ "
If you update your email"
+ " you will need to reconfirm your email address.
"
+ )
+ self.fields["email"].help_text = help_text
+ else:
+ help_text = "You have not confirmed your email address.
"
+ self.fields["email"].help_text = help_text
diff --git a/accounts/migrations/0008_userprofile_receiving_program_updates.py b/accounts/migrations/0008_userprofile_receiving_program_updates.py
index 4cbe23f0..82cd6d09 100644
--- a/accounts/migrations/0008_userprofile_receiving_program_updates.py
+++ b/accounts/migrations/0008_userprofile_receiving_program_updates.py
@@ -1,10 +1,11 @@
# Generated by Django 4.1.13 on 2024-02-15 19:42
+from __future__ import annotations
-from django.db import migrations, models
+from django.db import migrations
+from django.db import models
class Migration(migrations.Migration):
-
dependencies = [
("accounts", "0007_alter_userprofile_user"),
]
diff --git a/accounts/tests/test_activate_view.py b/accounts/tests/test_activate_view.py
index 5b39b2bc..bf44c1cb 100644
--- a/accounts/tests/test_activate_view.py
+++ b/accounts/tests/test_activate_view.py
@@ -1,4 +1,7 @@
-from django.test import Client, TestCase
+from __future__ import annotations
+
+from django.test import Client
+from django.test import TestCase
from django.urls import reverse
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
diff --git a/accounts/tests/test_profile_view.py b/accounts/tests/test_profile_view.py
index 6ab9e75d..b1f369d4 100644
--- a/accounts/tests/test_profile_view.py
+++ b/accounts/tests/test_profile_view.py
@@ -1,4 +1,7 @@
-from django.test import Client, TestCase
+from __future__ import annotations
+
+from django.test import Client
+from django.test import TestCase
from django.urls import reverse
from accounts.factories import ProfileFactory
@@ -13,6 +16,7 @@ def setUpTestData(cls):
profile = ProfileFactory.create(user__username="test")
cls.user = profile.user
cls.profile_url = reverse("profile")
+ cls.update_profile_url = reverse("update_user")
def test_redirect_when_unauthenticated(self):
response = self.client.get(self.profile_url, follow=True)
@@ -26,3 +30,35 @@ def test_profile(self):
self.assertContains(response, "Profile Info")
self.assertContains(response, "test")
self.assertContains(response, "Jane Doe")
+
+ def test_update_profile_initial_data(self):
+ self.client.force_login(self.user)
+ response = self.client.get(self.update_profile_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "Update Profile Info")
+ self.assertContains(response, "test")
+ self.assertContains(response, "Jane")
+ self.assertContains(response, "Doe")
+ self.assertContains(response, "You have not confirmed your email address.")
+
+ def test_update_profile(self):
+ self.client.force_login(self.user)
+ response = self.client.post(
+ self.update_profile_url,
+ data={
+ "username": "janedoe",
+ "email": "jane@newemail.com",
+ "first_name": "Jane",
+ "last_name": "Doe",
+ "receive_newsletter": True,
+ "receive_program_updates": True,
+ "receive_event_updates": True,
+ },
+ follow=True,
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "Profile Info")
+ self.user.profile.refresh_from_db()
+ self.assertEqual(self.user.profile.receiving_newsletter, True)
+ self.assertEqual(self.user.profile.receiving_event_updates, True)
+ self.assertEqual(self.user.profile.receiving_program_updates, True)
diff --git a/accounts/tests/test_signup_view.py b/accounts/tests/test_signup_view.py
index cedced0f..e57d996d 100644
--- a/accounts/tests/test_signup_view.py
+++ b/accounts/tests/test_signup_view.py
@@ -1,7 +1,10 @@
+from __future__ import annotations
+
from unittest.mock import patch
from django.core import mail
-from django.test import Client, TestCase
+from django.test import Client
+from django.test import TestCase
from django.urls import reverse
from accounts.models import CustomUser
@@ -17,7 +20,7 @@ def test_signup_template_renders(self):
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Registration")
- @patch("captcha.fields.ReCaptchaField.validate", return_value=True)
+ @patch("django_recaptcha.fields.ReCaptchaField.validate", return_value=True)
def test_signup_template_post_success(self, mock_captcha):
response = self.client.post(
self.url,
@@ -41,7 +44,10 @@ def test_signup_template_post_success(self, mock_captcha):
self.assertContains(response, "Registration")
self.assertContains(
response,
- "Your registration was successful. Please check your email provided for a confirmation link.",
+ (
+ "Your registration was successful."
+ " Please check your email provided for a confirmation link."
+ ),
)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(
diff --git a/accounts/tests/test_unsubscribe_view.py b/accounts/tests/test_unsubscribe_view.py
index 93903a4a..6c9f786a 100644
--- a/accounts/tests/test_unsubscribe_view.py
+++ b/accounts/tests/test_unsubscribe_view.py
@@ -1,4 +1,7 @@
-from django.test import Client, TestCase
+from __future__ import annotations
+
+from django.test import Client
+from django.test import TestCase
from django.urls import reverse
from accounts.factories import ProfileFactory
diff --git a/accounts/urls.py b/accounts/urls.py
index b6d0e45f..8972684e 100644
--- a/accounts/urls.py
+++ b/accounts/urls.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+from django.contrib.auth import views as auth_views
from django.urls import include
from django.urls import path
@@ -7,10 +8,17 @@
from .views import profile
from .views import SignUpView
from .views import unsubscribe
+from .views import UpdateUserView
urlpatterns = [
+ path(
+ "login/",
+ auth_views.LoginView.as_view(redirect_field_name="next_page"),
+ name="login",
+ ),
path("", include("django.contrib.auth.urls")),
path("profile/", profile, name="profile"),
+ path("profile/update/", UpdateUserView.as_view(), name="update_user"),
path("signup/", SignUpView.as_view(), name="signup"),
path(
"activate//",
diff --git a/accounts/views.py b/accounts/views.py
index e54d761f..dea43b62 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -19,7 +19,9 @@
from django.utils.http import urlsafe_base64_encode
from django.views import View
from django.views.generic.edit import CreateView
+from django.views.generic.edit import UpdateView
+from .forms import CustomUserChangeForm
from .forms import CustomUserCreationForm
from .tokens import account_activation_token
@@ -47,6 +49,30 @@ def get(self, request, uidb64, token):
return redirect("signup")
+def send_user_confirmation_email(request, user):
+ invite_link = reverse(
+ "activate_account",
+ kwargs={
+ "uidb64": urlsafe_base64_encode(force_bytes(user.pk)),
+ "token": account_activation_token.make_token(user),
+ },
+ )
+ unsubscribe_link = user.profile.create_unsubscribe_link()
+ email_dict = {
+ "cta_link": request.build_absolute_uri(invite_link),
+ "name": user.get_full_name(),
+ "unsubscribe_link": unsubscribe_link,
+ }
+ send_mail(
+ "Djangonaut Space Registration Confirmation",
+ render_to_string("emails/email_confirmation.txt", email_dict),
+ settings.DEFAULT_FROM_EMAIL,
+ [user.email],
+ html_message=render_to_string("emails/email_confirmation.html", email_dict),
+ fail_silently=False,
+ )
+
+
class SignUpView(CreateView):
form_class = CustomUserCreationForm
template_name = "registration/signup.html"
@@ -81,35 +107,69 @@ def form_valid(self, form):
"receiving_event_updates",
]
)
- invite_link = reverse(
- "activate_account",
- kwargs={
- "uidb64": urlsafe_base64_encode(force_bytes(user.pk)),
- "token": account_activation_token.make_token(user),
- },
- )
- unsubscribe_link = user.profile.create_unsubscribe_link()
- email_dict = {
- "cta_link": self.request.build_absolute_uri(invite_link),
- "name": user.get_full_name(),
- "unsubscribe_link": unsubscribe_link,
- }
- send_mail(
- "Djangonaut Space Registration Confirmation",
- render_to_string("emails/email_confirmation.txt", email_dict),
- settings.DEFAULT_FROM_EMAIL,
- [user.email],
- html_message=render_to_string("emails/email_confirmation.html", email_dict),
- fail_silently=False,
- )
+ send_user_confirmation_email(self.request, user)
return super().form_valid(form)
-@login_required(login_url="/accounts/login") # redirect when user is not logged in
+@login_required(login_url="/accounts/login")
def profile(request):
return render(request, "registration/profile.html")
+class UpdateUserView(UpdateView):
+ form_class = CustomUserChangeForm
+ template_name = "registration/update_user.html"
+
+ def get_object(self, queryset=None):
+ return self.request.user
+
+ def get_initial(self):
+ """
+ Returns the initial data to use for forms on this view.
+ """
+ initial = super().get_initial()
+ initial[
+ "receive_program_updates"
+ ] = self.request.user.profile.receiving_program_updates
+ initial[
+ "receive_event_updates"
+ ] = self.request.user.profile.receiving_event_updates
+ initial["receive_newsletter"] = self.request.user.profile.receiving_newsletter
+ return initial
+
+ def get_success_url(self):
+ messages.add_message(
+ self.request,
+ messages.INFO,
+ "Your profile information has been updated successfully.",
+ )
+ return reverse("profile")
+
+ def form_valid(self, form):
+ self.object = form.save()
+ user = self.object
+ user.profile.receiving_newsletter = form.cleaned_data["receive_newsletter"]
+ user.profile.receiving_program_updates = form.cleaned_data[
+ "receive_program_updates"
+ ]
+ user.profile.receiving_event_updates = form.cleaned_data[
+ "receive_event_updates"
+ ]
+ user.profile.save(
+ update_fields=[
+ "receiving_newsletter",
+ "receiving_program_updates",
+ "receiving_event_updates",
+ ]
+ )
+ """sends a link for a user to activate their account after changing their email"""
+ if "email" in form.changed_data:
+ user.profile.email_confirmed = False
+ user.profile.save()
+ send_user_confirmation_email(self.request, user)
+ return super().form_valid(form)
+
+
def unsubscribe(request, user_id, token):
"""
User is immediately unsubscribed if user is found. Otherwise, they are
diff --git a/home/admin.py b/home/admin.py
index 30c8bd9c..867b6021 100644
--- a/home/admin.py
+++ b/home/admin.py
@@ -2,7 +2,11 @@
from django.contrib import admin
-from .models import Event, Question, Session, SessionMembership, Survey
+from .models import Event
+from .models import Question
+from .models import Session
+from .models import SessionMembership
+from .models import Survey
@admin.register(Event)
diff --git a/home/constants.py b/home/constants.py
index 728bdcad..3aed2060 100644
--- a/home/constants.py
+++ b/home/constants.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
DATE_INPUT_FORMAT = ["%Y-%m-%d", "%m/%d/%Y", "%m/%d/%y", "%d/%m/%y", "%d/%m/%Y"]
SURVEY_FIELD_VALIDATORS = {
diff --git a/home/factories.py b/home/factories.py
index 418f8040..465a45ee 100644
--- a/home/factories.py
+++ b/home/factories.py
@@ -1,15 +1,15 @@
+from __future__ import annotations
+
import factory
from accounts.factories import UserFactory
-from home.models import (
- Event,
- Question,
- Session,
- Survey,
- TypeField,
- UserQuestionResponse,
- UserSurveyResponse,
-)
+from home.models import Event
+from home.models import Question
+from home.models import Session
+from home.models import Survey
+from home.models import TypeField
+from home.models import UserQuestionResponse
+from home.models import UserSurveyResponse
class EventFactory(factory.django.DjangoModelFactory):
diff --git a/home/management/commands/applications_open_email.py b/home/management/commands/applications_open_email.py
index c632775e..87f4a69c 100644
--- a/home/management/commands/applications_open_email.py
+++ b/home/management/commands/applications_open_email.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from django.conf import settings
from django.core.mail import send_mail
from django.core.management.base import BaseCommand
@@ -10,14 +12,14 @@
class Command(BaseCommand):
help = """
- Checks if applications are open for a program session
+ Checks if applications are open for a program session
and notify interested folk via email.
To be ran once a day.
"""
def handle(self, *args, **options):
try:
- session_applications_starting_today = Session.objects.get(
+ applications_starting_today = Session.objects.get(
application_start_date=timezone.now().date()
)
except Session.DoesNotExist:
@@ -37,18 +39,16 @@ def handle(self, *args, **options):
.iterator()
):
email_data = {
- "title": session_applications_starting_today.title,
- "detail_url": session_applications_starting_today.get_full_url(),
- "start_date": session_applications_starting_today.start_date.strftime(
- "%b %d, %Y"
- ),
- "end_date": session_applications_starting_today.end_date.strftime(
+ "title": applications_starting_today.title,
+ "detail_url": applications_starting_today.get_full_url(),
+ "start_date": applications_starting_today.start_date.strftime(
"%b %d, %Y"
),
- "application_end_date": session_applications_starting_today.application_end_date.strftime(
+ "end_date": applications_starting_today.end_date.strftime("%b %d, %Y"),
+ "application_end_date": applications_starting_today.application_end_date.strftime(
"%b %d, %Y"
),
- "cta_link": session_applications_starting_today.application_url,
+ "cta_link": applications_starting_today.get_application_url(),
"name": user.get_full_name(),
"unsubscribe_link": user.profile.create_unsubscribe_link(),
}
@@ -66,6 +66,6 @@ def handle(self, *args, **options):
self.stdout.write(
self.style.SUCCESS(
f"Application open notification sent to {emails_sent} prospective Djangonauts "
- f"for session '{session_applications_starting_today.title}'!"
+ f"for session '{applications_starting_today.title}'!"
)
)
diff --git a/home/managers.py b/home/managers.py
index 220a2851..7f03c569 100644
--- a/home/managers.py
+++ b/home/managers.py
@@ -1,5 +1,8 @@
from __future__ import annotations
+from django.db.models import Exists
+from django.db.models import OuterRef
+from django.db.models import Value
from django.db.models.query import QuerySet
from django.utils import timezone
@@ -27,6 +30,21 @@ def past(self):
return self.filter(start_time__lte=timezone.now())
+class SessionQuerySet(QuerySet):
+ def with_applications(self, user):
+ from home.models import UserSurveyResponse
+
+ if user.is_anonymous:
+ return self.annotate(completed_application=Value(False))
+ return self.annotate(
+ completed_application=Exists(
+ UserSurveyResponse.objects.filter(
+ survey_id=OuterRef("application_survey_id"), user_id=user.id
+ )
+ )
+ )
+
+
class SessionMembershipQuerySet(QuerySet):
def _SessionMembership(self):
return self.model.session._meta.model
diff --git a/home/migrations/0017_generalpage_generaltag_generalpage_tags.py b/home/migrations/0017_generalpage_generaltag_generalpage_tags.py
index 9a17c67b..7481f39f 100644
--- a/home/migrations/0017_generalpage_generaltag_generalpage_tags.py
+++ b/home/migrations/0017_generalpage_generaltag_generalpage_tags.py
@@ -1,6 +1,6 @@
# Generated by Django 4.1.5 on 2023-12-11 04:42
+from __future__ import annotations
-from django.db import migrations, models
import django.db.models.deletion
import modelcluster.contrib.taggit
import modelcluster.fields
@@ -8,6 +8,8 @@
import wagtail.contrib.table_block.blocks
import wagtail.fields
import wagtail.images.blocks
+from django.db import migrations
+from django.db import models
class Migration(migrations.Migration):
diff --git a/home/migrations/0018_alter_generalpage_content.py b/home/migrations/0018_alter_generalpage_content.py
index 23932b9f..4f3f644d 100644
--- a/home/migrations/0018_alter_generalpage_content.py
+++ b/home/migrations/0018_alter_generalpage_content.py
@@ -1,10 +1,11 @@
# Generated by Django 4.1.13 on 2023-12-29 06:35
+from __future__ import annotations
-from django.db import migrations
import wagtail.blocks
import wagtail.contrib.table_block.blocks
import wagtail.fields
import wagtail.images.blocks
+from django.db import migrations
class Migration(migrations.Migration):
diff --git a/home/migrations/0021_question_survey_usersurveyresponse_and_more.py b/home/migrations/0021_question_survey_usersurveyresponse_and_more.py
index cf8696cd..3ffe71d1 100644
--- a/home/migrations/0021_question_survey_usersurveyresponse_and_more.py
+++ b/home/migrations/0021_question_survey_usersurveyresponse_and_more.py
@@ -1,12 +1,13 @@
# Generated by Django 4.1.13 on 2024-02-23 07:56
+from __future__ import annotations
-from django.conf import settings
-from django.db import migrations, models
import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations
+from django.db import models
class Migration(migrations.Migration):
-
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("home", "0020_alter_generalpage_content"),
diff --git a/home/migrations/0023_alter_question_choices_alter_question_type_field_and_more.py b/home/migrations/0023_alter_question_choices_alter_question_type_field_and_more.py
index 1d7aee66..3573dbdd 100644
--- a/home/migrations/0023_alter_question_choices_alter_question_type_field_and_more.py
+++ b/home/migrations/0023_alter_question_choices_alter_question_type_field_and_more.py
@@ -1,29 +1,52 @@
# Generated by Django 4.1.13 on 2024-02-25 19:25
+from __future__ import annotations
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations
+from django.db import models
class Migration(migrations.Migration):
-
dependencies = [
- ('home', '0022_remove_signuppage_page_ptr_delete_signupfield_and_more'),
+ ("home", "0022_remove_signuppage_page_ptr_delete_signupfield_and_more"),
]
operations = [
migrations.AlterField(
- model_name='question',
- name='choices',
- field=models.TextField(blank=True, help_text='If type field is radio, select, or multi select, fill in the options separated by commas. Ex: Male, Female.
If type field is rating, use a number such as 5.'),
+ model_name="question",
+ name="choices",
+ field=models.TextField(
+ blank=True,
+ help_text="If type field is radio, select, or multi select, fill in the options separated by commas. Ex: Male, Female.
If type field is rating, use a number such as 5.",
+ ),
),
migrations.AlterField(
- model_name='question',
- name='type_field',
- field=models.CharField(choices=[('TEXT', 'Text'), ('NUMBER', 'Number'), ('DATE', 'Date'), ('RADIO', 'Radio'), ('SELECT', 'Select'), ('MULTI_SELECT', 'Multi Select'), ('TEXT_AREA', 'Text Area'), ('URL', 'URL'), ('EMAIL', 'Email'), ('RATING', 'Rating')], max_length=100),
+ model_name="question",
+ name="type_field",
+ field=models.CharField(
+ choices=[
+ ("TEXT", "Text"),
+ ("NUMBER", "Number"),
+ ("DATE", "Date"),
+ ("RADIO", "Radio"),
+ ("SELECT", "Select"),
+ ("MULTI_SELECT", "Multi Select"),
+ ("TEXT_AREA", "Text Area"),
+ ("URL", "URL"),
+ ("EMAIL", "Email"),
+ ("RATING", "Rating"),
+ ],
+ max_length=100,
+ ),
),
migrations.AlterField(
- model_name='survey',
- name='session',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.session'),
+ model_name="survey",
+ name="session",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to="home.session",
+ ),
),
]
diff --git a/home/migrations/0024_alter_question_help_text_alter_question_key.py b/home/migrations/0024_alter_question_help_text_alter_question_key.py
new file mode 100644
index 00000000..77090aa1
--- /dev/null
+++ b/home/migrations/0024_alter_question_help_text_alter_question_key.py
@@ -0,0 +1,31 @@
+# Generated by Django 4.1.13 on 2024-03-26 14:57
+from __future__ import annotations
+
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("home", "0023_alter_question_choices_alter_question_type_field_and_more"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="question",
+ name="help_text",
+ field=models.TextField(
+ blank=True, help_text="You can add a help text in here."
+ ),
+ ),
+ migrations.AlterField(
+ model_name="question",
+ name="key",
+ field=models.CharField(
+ blank=True,
+ help_text="Unique key for this question, fill in the blank if you want to use for automatic generation.",
+ max_length=500,
+ unique=True,
+ ),
+ ),
+ ]
diff --git a/home/migrations/0025_alter_generalpage_content.py b/home/migrations/0025_alter_generalpage_content.py
new file mode 100644
index 00000000..99443e68
--- /dev/null
+++ b/home/migrations/0025_alter_generalpage_content.py
@@ -0,0 +1,269 @@
+# Generated by Django 4.1.13 on 2024-03-26 21:21
+from __future__ import annotations
+
+import wagtail.blocks
+import wagtail.fields
+import wagtail.images.blocks
+from django.db import migrations
+
+import home.blocks
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("home", "0024_alter_question_help_text_alter_question_key"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="generalpage",
+ name="content",
+ field=wagtail.fields.StreamField(
+ [
+ (
+ "heading",
+ wagtail.blocks.StructBlock(
+ [
+ (
+ "size",
+ wagtail.blocks.ChoiceBlock(
+ choices=[
+ ("text-5xl", "h1"),
+ ("text-4xl", "h2"),
+ ("text-3xl", "h3"),
+ ("text-2xl", "h4"),
+ ("text-xl", "h5"),
+ ("text-lg", "h6"),
+ ],
+ icon="title",
+ ),
+ ),
+ (
+ "heading",
+ wagtail.blocks.CharBlock(
+ class_name="heading-blog", max_length=255
+ ),
+ ),
+ ],
+ icon="h1",
+ label="Heading",
+ ),
+ ),
+ (
+ "rich_text",
+ wagtail.blocks.StructBlock(
+ [
+ (
+ "text",
+ wagtail.blocks.RichTextBlock(
+ features=[
+ "embed",
+ "bold",
+ "italic",
+ "link",
+ "superscript",
+ "subscript",
+ "strikethrough",
+ "code",
+ "hr",
+ ],
+ icon="title",
+ label="Rich Text",
+ max_length=10000,
+ ),
+ )
+ ]
+ ),
+ ),
+ (
+ "list",
+ wagtail.blocks.StructBlock(
+ [
+ (
+ "size",
+ wagtail.blocks.ChoiceBlock(
+ choices=[
+ ("circle", "unordered list"),
+ ("decimal", "ordered list"),
+ ("none", "unstyled"),
+ ]
+ ),
+ ),
+ (
+ "text",
+ wagtail.blocks.RichTextBlock(
+ features=["ul"], icon="list-ol"
+ ),
+ ),
+ ],
+ icon="list-ol",
+ label="List",
+ ),
+ ),
+ ("paragraph", wagtail.blocks.TextBlock(max_length=10000)),
+ (
+ "html",
+ wagtail.blocks.RawHTMLBlock(icon="code", label="Raw HTML"),
+ ),
+ ("image", wagtail.images.blocks.ImageChooserBlock()),
+ (
+ "caption",
+ wagtail.blocks.StructBlock(
+ [("text", wagtail.blocks.TextBlock())]
+ ),
+ ),
+ (
+ "text_with_heading",
+ wagtail.blocks.StructBlock(
+ [
+ (
+ "heading",
+ wagtail.blocks.CharBlock(
+ class_name="heading-blog", max_length=255
+ ),
+ ),
+ ("text", wagtail.blocks.TextBlock()),
+ ]
+ ),
+ ),
+ (
+ "text_with_heading_and_image",
+ wagtail.blocks.StructBlock(
+ [
+ ("heading", wagtail.blocks.CharBlock(max_length=255)),
+ ("text", wagtail.blocks.TextBlock()),
+ ("image", wagtail.images.blocks.ImageChooserBlock()),
+ ]
+ ),
+ ),
+ (
+ "text_with_heading_and_right_image",
+ wagtail.blocks.StructBlock(
+ [
+ (
+ "heading",
+ wagtail.blocks.CharBlock(
+ class_name="heading-blog", max_length=255
+ ),
+ ),
+ ("text", wagtail.blocks.TextBlock()),
+ ("image", wagtail.images.blocks.ImageChooserBlock()),
+ ]
+ ),
+ ),
+ (
+ "text_with_heading_and_left_image",
+ wagtail.blocks.StructBlock(
+ [
+ (
+ "heading",
+ wagtail.blocks.CharBlock(
+ class_name="blog", max_length=255
+ ),
+ ),
+ ("text", wagtail.blocks.TextBlock()),
+ ("image", wagtail.images.blocks.ImageChooserBlock()),
+ ]
+ ),
+ ),
+ (
+ "right_image_left_text",
+ wagtail.blocks.StructBlock(
+ [
+ ("image", wagtail.images.blocks.ImageChooserBlock()),
+ ("text", wagtail.blocks.TextBlock()),
+ ]
+ ),
+ ),
+ (
+ "left_image_right_text",
+ wagtail.blocks.StructBlock(
+ [
+ ("image", wagtail.images.blocks.ImageChooserBlock()),
+ ("text", wagtail.blocks.TextBlock()),
+ ]
+ ),
+ ),
+ (
+ "left_quote_right_image",
+ wagtail.blocks.StructBlock(
+ [
+ ("quote", wagtail.blocks.TextBlock()),
+ ("byline", wagtail.blocks.CharBlock(max_length=255)),
+ ("image", wagtail.images.blocks.ImageChooserBlock()),
+ ],
+ icon="openquote",
+ ),
+ ),
+ (
+ "video_embed",
+ wagtail.blocks.StructBlock(
+ [
+ ("heading", wagtail.blocks.CharBlock(max_length=255)),
+ ("text", wagtail.blocks.TextBlock()),
+ ]
+ ),
+ ),
+ ("table", home.blocks.CustomTableBlock()),
+ (
+ "code_block",
+ wagtail.blocks.StructBlock(
+ [
+ (
+ "language",
+ wagtail.blocks.ChoiceBlock(
+ choices=[
+ ("Python", "python"),
+ ("Markup", "html"),
+ ("CSS", "css"),
+ ("Clojure", "clojure"),
+ ("Bash", "shell"),
+ ("Django", "django"),
+ ("Jinja2", "jinja2"),
+ ("Docker", "dockerfile"),
+ ("Git", "git"),
+ ("GraphQL", "graphql"),
+ ("Handlebars", "handlebars"),
+ (".ignore", "gitignore"),
+ ("JSON", "json"),
+ ("JSON5", "json5"),
+ ("Markdown", "md"),
+ ("Markdown", "md"),
+ ("React JSX", "jsx"),
+ ("React TSX", "tsx"),
+ ("SASS", "sass"),
+ ("SCSS", "scss"),
+ ("TypeScript", "ts"),
+ ("vim", "vim"),
+ ]
+ ),
+ ),
+ (
+ "caption",
+ wagtail.blocks.CharBlock(
+ blank=True, max_length=255
+ ),
+ ),
+ (
+ "page",
+ wagtail.blocks.CharBlock(
+ blank=True, max_length=255
+ ),
+ ),
+ (
+ "code",
+ wagtail.blocks.TextBlock(
+ blank=True, max_length=1000
+ ),
+ ),
+ ]
+ ),
+ ),
+ ],
+ blank=True,
+ null=True,
+ use_json_field=True,
+ verbose_name="StreamField Body",
+ ),
+ ),
+ ]
diff --git a/home/migrations/0026_session_application_survey_and_more.py b/home/migrations/0026_session_application_survey_and_more.py
new file mode 100644
index 00000000..34139242
--- /dev/null
+++ b/home/migrations/0026_session_application_survey_and_more.py
@@ -0,0 +1,35 @@
+# Generated by Django 4.1.13 on 2024-03-27 17:27
+from __future__ import annotations
+
+import django.db.models.deletion
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("home", "0025_alter_generalpage_content"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="session",
+ name="application_survey",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="application_sessions",
+ to="home.survey",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="session",
+ name="application_url",
+ field=models.URLField(
+ blank=True,
+ help_text="This is a URL to the Djangonaut application form. Likely Google Forms.",
+ null=True,
+ ),
+ ),
+ ]
diff --git a/home/models/__init__.py b/home/models/__init__.py
index 95d16efa..e9fa390c 100644
--- a/home/models/__init__.py
+++ b/home/models/__init__.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from .blog import *
from .event import *
from .session import *
diff --git a/home/models/blog.py b/home/models/blog.py
index 01855cf9..1cfd04f8 100644
--- a/home/models/blog.py
+++ b/home/models/blog.py
@@ -91,22 +91,8 @@ class GeneralPage(Page):
tags = ClusterTaggableManager(through=GeneralTag, blank=True)
date = models.DateTimeField("Post Date")
content = StreamField(
- [
- ("heading", blog_blocks.HeadingBlock(class_name="full")),
- ("subheading", blocks.CharBlock(class_name="full")),
- ("paragraph", blocks.RichTextBlock(class_name="full")),
- ("HTML", blocks.RawHTMLBlock(class_name="full")),
- ("image", ImageChooserBlock()),
- ("text_with_heading", blog_blocks.HeadingBlock(class_name="full")),
- (
- "text_heading_image",
- blog_blocks.TextHeadingImageBlock(class_name="full"),
- ),
- ("video_embed", blog_blocks.VideoEmbed(class_name="full")),
- ("table", TableBlock(class_name="full")),
- ("code_block", blog_blocks.CodeBlock(class_name="full")),
- ("quote_block", blog_blocks.QuoteBlock(class_name="full")),
- ],
+ BaseStreamBlock(),
+ verbose_name="StreamField Body",
blank=True,
null=True,
use_json_field=True,
diff --git a/home/models/session.py b/home/models/session.py
index 2286de7f..550507e9 100644
--- a/home/models/session.py
+++ b/home/models/session.py
@@ -9,6 +9,7 @@
from django.utils.translation import gettext_lazy as _
from home.managers import SessionMembershipQuerySet
+from home.managers import SessionQuerySet
class Session(models.Model):
@@ -39,10 +40,21 @@ class Session(models.Model):
application_end_date = models.DateField(
help_text="This is the end date for Djangonaut applications."
)
+ application_survey = models.ForeignKey(
+ "home.Survey",
+ related_name="application_sessions",
+ on_delete=models.SET_NULL,
+ null=True,
+ blank=True,
+ )
application_url = models.URLField(
- help_text="This is a URL to the Djangonaut application form. Likely Google Forms."
+ help_text="This is a URL to the Djangonaut application form. Likely Google Forms.",
+ null=True,
+ blank=True,
)
+ objects = models.Manager.from_queryset(SessionQuerySet)()
+
def __str__(self):
return self.title
@@ -70,6 +82,11 @@ def is_accepting_applications(self):
<= self.application_end_anywhere_on_earth()
)
+ def get_application_url(self):
+ if self.application_survey:
+ return self.application_survey.get_survey_response_url()
+ return self.application_url
+
def get_absolute_url(self):
return reverse("session_detail", kwargs={"slug": self.slug})
diff --git a/home/models/survey.py b/home/models/survey.py
index 3c7122f1..d8f03156 100644
--- a/home/models/survey.py
+++ b/home/models/survey.py
@@ -1,6 +1,7 @@
from __future__ import annotations
from django.db import models
+from django.urls import reverse
from django.utils.safestring import mark_safe
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
@@ -24,11 +25,16 @@ class Survey(BaseModel):
deletable = models.BooleanField(
default=True, help_text=_("If False, user can't delete record.")
)
- session = models.ForeignKey("home.Session", on_delete=models.SET_NULL, null=True, blank=True)
+ session = models.ForeignKey(
+ "home.Session", on_delete=models.SET_NULL, null=True, blank=True
+ )
def __str__(self):
return self.name
+ def get_survey_response_url(self):
+ return reverse("survey_response_create", kwargs={"slug": self.slug})
+
def save(
self, force_insert=False, force_update=False, using=None, update_fields=None
):
@@ -58,7 +64,7 @@ class TypeField(models.TextChoices):
class Question(BaseModel):
key = models.CharField(
- max_length=225,
+ max_length=500,
unique=True,
blank=True,
help_text=_(
@@ -81,8 +87,7 @@ class Question(BaseModel):
"If type field is rating, use a number such as 5."
),
)
- help_text = models.CharField(
- max_length=200,
+ help_text = models.TextField(
blank=True,
help_text=_("You can add a help text in here."),
)
diff --git a/home/puput_migrations/0001_initial.py b/home/puput_migrations/0001_initial.py
index af4fd653..0cb1dec8 100644
--- a/home/puput_migrations/0001_initial.py
+++ b/home/puput_migrations/0001_initial.py
@@ -1,8 +1,9 @@
# Generated by Django 4.1.5 on 2023-12-11 05:50
+from __future__ import annotations
-import colorful.fields
import datetime
-from django.db import migrations, models
+
+import colorful.fields
import django.db.models.deletion
import django.db.models.manager
import modelcluster.contrib.taggit
@@ -12,6 +13,8 @@
import wagtail.contrib.table_block.blocks
import wagtail.fields
import wagtail.images.blocks
+from django.db import migrations
+from django.db import models
class Migration(migrations.Migration):
diff --git a/home/puput_migrations/0002_entrypage_markdown_body.py b/home/puput_migrations/0002_entrypage_markdown_body.py
index a54b430f..a3958136 100644
--- a/home/puput_migrations/0002_entrypage_markdown_body.py
+++ b/home/puput_migrations/0002_entrypage_markdown_body.py
@@ -1,7 +1,8 @@
# Generated by Django 4.1.13 on 2023-12-11 07:40
+from __future__ import annotations
-from django.db import migrations
import wagtailmarkdown.fields
+from django.db import migrations
class Migration(migrations.Migration):
diff --git a/home/puput_migrations/0003_alter_entrypage_body.py b/home/puput_migrations/0003_alter_entrypage_body.py
index 39e0d8c4..5e8ae926 100644
--- a/home/puput_migrations/0003_alter_entrypage_body.py
+++ b/home/puput_migrations/0003_alter_entrypage_body.py
@@ -1,21 +1,180 @@
# Generated by Django 4.1.5 on 2023-12-27 17:52
+from __future__ import annotations
-from django.db import migrations
import wagtail.blocks
import wagtail.contrib.table_block.blocks
import wagtail.fields
import wagtail.images.blocks
+from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
- ('puput', '0002_entrypage_markdown_body'),
+ ("puput", "0002_entrypage_markdown_body"),
]
operations = [
migrations.AlterField(
- model_name='entrypage',
- name='body',
- field=wagtail.fields.StreamField([('heading', wagtail.blocks.StructBlock([('heading', wagtail.blocks.CharBlock(class_name='heading-blog', max_length=255))])), ('paragraph', wagtail.blocks.CharBlock(max_length=255)), ('html', wagtail.blocks.RawHTMLBlock(icon='code', label='Raw HTML')), ('image', wagtail.images.blocks.ImageChooserBlock()), ('text_with_heading', wagtail.blocks.StructBlock([('heading', wagtail.blocks.CharBlock(max_length=255)), ('text', wagtail.blocks.TextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock())])), ('text_with_heading_and_right_image', wagtail.blocks.StructBlock([('heading', wagtail.blocks.CharBlock(class_name='heading-blog', max_length=255)), ('text', wagtail.blocks.TextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock())])), ('text_with_heading_and_left_image', wagtail.blocks.StructBlock([('heading', wagtail.blocks.CharBlock(class_name='blog', max_length=255)), ('text', wagtail.blocks.TextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock())])), ('right_image_left_text', wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('text', wagtail.blocks.TextBlock())])), ('left_image_right_text', wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('text', wagtail.blocks.TextBlock())])), ('left_quote_right_image', wagtail.blocks.StructBlock([('quote', wagtail.blocks.TextBlock()), ('byline', wagtail.blocks.CharBlock(max_length=255)), ('image', wagtail.images.blocks.ImageChooserBlock())])), ('video_embed', wagtail.blocks.StructBlock([('heading', wagtail.blocks.CharBlock(max_length=255)), ('text', wagtail.blocks.TextBlock())])), ('table', wagtail.contrib.table_block.blocks.TableBlock()), ('code_block', wagtail.blocks.StructBlock([('language', wagtail.blocks.ChoiceBlock(choices=[('Python', 'python'), ('Markup', 'html'), ('CSS', 'css'), ('Clojure', 'clojure'), ('Bash', 'shell'), ('Django', 'django'), ('Jinja2', 'jinja2'), ('Docker', 'dockerfile'), ('Git', 'git'), ('GraphQL', 'graphql'), ('Handlebars', 'handlebars'), ('.ignore', 'gitignore'), ('JSON', 'json'), ('JSON5', 'json5'), ('Markdown', 'md'), ('Markdown', 'md'), ('React JSX', 'jsx'), ('React TSX', 'tsx'), ('SASS', 'sass'), ('SCSS', 'scss'), ('TypeScript', 'ts'), ('vim', 'vim')])), ('caption', wagtail.blocks.CharBlock(blank=True, max_length=255)), ('page', wagtail.blocks.CharBlock(blank=True, max_length=255)), ('code', wagtail.blocks.TextBlock(blank=True, max_length=1000))])), ('rich_text', wagtail.blocks.RichTextBlock())], null=True, use_json_field=True, verbose_name='StreamField Body'),
+ model_name="entrypage",
+ name="body",
+ field=wagtail.fields.StreamField(
+ [
+ (
+ "heading",
+ wagtail.blocks.StructBlock(
+ [
+ (
+ "heading",
+ wagtail.blocks.CharBlock(
+ class_name="heading-blog", max_length=255
+ ),
+ )
+ ]
+ ),
+ ),
+ ("paragraph", wagtail.blocks.CharBlock(max_length=255)),
+ (
+ "html",
+ wagtail.blocks.RawHTMLBlock(icon="code", label="Raw HTML"),
+ ),
+ ("image", wagtail.images.blocks.ImageChooserBlock()),
+ (
+ "text_with_heading",
+ wagtail.blocks.StructBlock(
+ [
+ ("heading", wagtail.blocks.CharBlock(max_length=255)),
+ ("text", wagtail.blocks.TextBlock()),
+ ("image", wagtail.images.blocks.ImageChooserBlock()),
+ ]
+ ),
+ ),
+ (
+ "text_with_heading_and_right_image",
+ wagtail.blocks.StructBlock(
+ [
+ (
+ "heading",
+ wagtail.blocks.CharBlock(
+ class_name="heading-blog", max_length=255
+ ),
+ ),
+ ("text", wagtail.blocks.TextBlock()),
+ ("image", wagtail.images.blocks.ImageChooserBlock()),
+ ]
+ ),
+ ),
+ (
+ "text_with_heading_and_left_image",
+ wagtail.blocks.StructBlock(
+ [
+ (
+ "heading",
+ wagtail.blocks.CharBlock(
+ class_name="blog", max_length=255
+ ),
+ ),
+ ("text", wagtail.blocks.TextBlock()),
+ ("image", wagtail.images.blocks.ImageChooserBlock()),
+ ]
+ ),
+ ),
+ (
+ "right_image_left_text",
+ wagtail.blocks.StructBlock(
+ [
+ ("image", wagtail.images.blocks.ImageChooserBlock()),
+ ("text", wagtail.blocks.TextBlock()),
+ ]
+ ),
+ ),
+ (
+ "left_image_right_text",
+ wagtail.blocks.StructBlock(
+ [
+ ("image", wagtail.images.blocks.ImageChooserBlock()),
+ ("text", wagtail.blocks.TextBlock()),
+ ]
+ ),
+ ),
+ (
+ "left_quote_right_image",
+ wagtail.blocks.StructBlock(
+ [
+ ("quote", wagtail.blocks.TextBlock()),
+ ("byline", wagtail.blocks.CharBlock(max_length=255)),
+ ("image", wagtail.images.blocks.ImageChooserBlock()),
+ ]
+ ),
+ ),
+ (
+ "video_embed",
+ wagtail.blocks.StructBlock(
+ [
+ ("heading", wagtail.blocks.CharBlock(max_length=255)),
+ ("text", wagtail.blocks.TextBlock()),
+ ]
+ ),
+ ),
+ ("table", wagtail.contrib.table_block.blocks.TableBlock()),
+ (
+ "code_block",
+ wagtail.blocks.StructBlock(
+ [
+ (
+ "language",
+ wagtail.blocks.ChoiceBlock(
+ choices=[
+ ("Python", "python"),
+ ("Markup", "html"),
+ ("CSS", "css"),
+ ("Clojure", "clojure"),
+ ("Bash", "shell"),
+ ("Django", "django"),
+ ("Jinja2", "jinja2"),
+ ("Docker", "dockerfile"),
+ ("Git", "git"),
+ ("GraphQL", "graphql"),
+ ("Handlebars", "handlebars"),
+ (".ignore", "gitignore"),
+ ("JSON", "json"),
+ ("JSON5", "json5"),
+ ("Markdown", "md"),
+ ("Markdown", "md"),
+ ("React JSX", "jsx"),
+ ("React TSX", "tsx"),
+ ("SASS", "sass"),
+ ("SCSS", "scss"),
+ ("TypeScript", "ts"),
+ ("vim", "vim"),
+ ]
+ ),
+ ),
+ (
+ "caption",
+ wagtail.blocks.CharBlock(
+ blank=True, max_length=255
+ ),
+ ),
+ (
+ "page",
+ wagtail.blocks.CharBlock(
+ blank=True, max_length=255
+ ),
+ ),
+ (
+ "code",
+ wagtail.blocks.TextBlock(
+ blank=True, max_length=1000
+ ),
+ ),
+ ]
+ ),
+ ),
+ ("rich_text", wagtail.blocks.RichTextBlock()),
+ ],
+ null=True,
+ use_json_field=True,
+ verbose_name="StreamField Body",
+ ),
),
]
diff --git a/home/puput_migrations/0004_alter_entrypage_body.py b/home/puput_migrations/0004_alter_entrypage_body.py
index 46a1e3bd..361a422d 100644
--- a/home/puput_migrations/0004_alter_entrypage_body.py
+++ b/home/puput_migrations/0004_alter_entrypage_body.py
@@ -1,10 +1,12 @@
# Generated by Django 4.1.13 on 2023-12-29 09:41
+from __future__ import annotations
-from django.db import migrations
-import home.blocks
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
+from django.db import migrations
+
+import home.blocks
class Migration(migrations.Migration):
diff --git a/home/static/css/blog.css b/home/static/css/blog.css
index e9a74c8a..d2c4f5b1 100644
--- a/home/static/css/blog.css
+++ b/home/static/css/blog.css
@@ -1,5 +1,5 @@
.blog-container .article .social-share:after,
-.blog-container .social-share-all:before
+.blog-container .social-share-all:before
{
display: none;
}
@@ -72,4 +72,4 @@ ul.sidebar h4 {
justify-content: center;
height: 100%;
}
-}
\ No newline at end of file
+}
diff --git a/home/templates/home/general_page.html b/home/templates/home/general_page.html
index 9c1387fa..a01f7cad 100644
--- a/home/templates/home/general_page.html
+++ b/home/templates/home/general_page.html
@@ -3,46 +3,41 @@
{% block body_class %}template-generalpage{% endblock %}
-{% block extra_css %}
-
-{% endblock extra_css %}
-
-{% block extra_js %}
-
-
-{% endblock extra_js %}
-
{% block content %}
-
-
-
-
-
- {{ page.content }}
-
-
+
+
+
+
+
+
+ -
+
+ {{ page.owner }}
+
+ -
+
+ {{ page.date|date:"DATE_FORMAT" }}
+
+ -
+ {% for tag in page.tags.all %}
+ {{ tag }}
+ {% endfor %}
+
+
+
+ {{ page.intro|richtext }}
+
+
+
+
{{ page.body|richtext }}
-
-
-
-
+
+
+
+
{% endblock %}
diff --git a/home/templates/home/includes/email_confirmed_warning.html b/home/templates/home/includes/email_confirmed_warning.html
new file mode 100644
index 00000000..c81f142e
--- /dev/null
+++ b/home/templates/home/includes/email_confirmed_warning.html
@@ -0,0 +1,6 @@
+{% if request.user.is_authenticated and not request.user.profile.email_confirmed %}
+
+
Your email is not confirmed!
+
You may not be able to apply for sessions without confirming your email address.
+
+{% endif %}
diff --git a/home/templates/home/includes/session_apply_btn.html b/home/templates/home/includes/session_apply_btn.html
new file mode 100644
index 00000000..0a6e9fca
--- /dev/null
+++ b/home/templates/home/includes/session_apply_btn.html
@@ -0,0 +1,7 @@
+{% if session.is_accepting_applications %}
+ {% if session.application_url %}
+
Apply
+ {% elif request.user.is_authenticated and not session.completed_application and request.user.profile.email_confirmed %}
+
Apply
+ {% endif %}
+{% endif %}
diff --git a/home/templates/home/includes/session_card.html b/home/templates/home/includes/session_card.html
index 8ecab07f..84c81728 100644
--- a/home/templates/home/includes/session_card.html
+++ b/home/templates/home/includes/session_card.html
@@ -17,9 +17,7 @@
Starts
{{ session.start_date|date:"M d, Y" }}
diff --git a/home/templates/home/prerelease/session_detail.html b/home/templates/home/prerelease/session_detail.html
index 45650205..fd2268e9 100644
--- a/home/templates/home/prerelease/session_detail.html
+++ b/home/templates/home/prerelease/session_detail.html
@@ -14,6 +14,7 @@
{% endblock social_share %}
{% block content %}
+{% include 'home/includes/email_confirmed_warning.html' %}
@@ -35,12 +36,12 @@
{{ session.title }}
{{ session.end_date|date:"M d, Y" }}
{{ session.description|linebreaksbr|urlizetrunc:25 }}
- {% if session.is_accepting_applications %}
+ {% if session.is_accepting_applications and not session.completed_application %}
You have {{ session.application_end_anywhere_on_earth|timeuntil}} to submit your application
-
Apply
{% endif %}
+ {% include 'home/includes/session_apply_btn.html' %}
diff --git a/home/templates/home/prerelease/session_list.html b/home/templates/home/prerelease/session_list.html
index 4440a0db..fba27f67 100644
--- a/home/templates/home/prerelease/session_list.html
+++ b/home/templates/home/prerelease/session_list.html
@@ -9,6 +9,7 @@
{% block content %}
+{% include 'home/includes/email_confirmed_warning.html' %}
Sessions
diff --git a/home/templates/home/surveys/form.html b/home/templates/home/surveys/form.html
index b6489e50..f964ea40 100644
--- a/home/templates/home/surveys/form.html
+++ b/home/templates/home/surveys/form.html
@@ -11,14 +11,14 @@
{{ title_page }}
- {{ sub_title_page }}
+ {{ sub_title_page|linebreaks|urlize }}
diff --git a/indymeet/templates/django_social_share/templatetags/post_to_twitter.html b/indymeet/templates/django_social_share/templatetags/post_to_twitter.html
index 20dd9786..a3dbcbfc 100644
--- a/indymeet/templates/django_social_share/templatetags/post_to_twitter.html
+++ b/indymeet/templates/django_social_share/templatetags/post_to_twitter.html
@@ -1,3 +1,3 @@
\ No newline at end of file
+
diff --git a/indymeet/templates/emails/base.html b/indymeet/templates/emails/base.html
index 30733eb2..3c78bd10 100644
--- a/indymeet/templates/emails/base.html
+++ b/indymeet/templates/emails/base.html
@@ -146,4 +146,4 @@