6 |
--------------------------------------------------------------------------------
/templates/mfa/email/webauthn_added_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% autoescape off %}
3 | {% blocktrans %}Security Key Added{% endblocktrans %}
4 | {% endautoescape %}
5 |
--------------------------------------------------------------------------------
/templates/account/email/email_confirm_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% autoescape off %}
3 | {% blocktrans %}Email Confirmation{% endblocktrans %}
4 | {% endautoescape %}
5 |
--------------------------------------------------------------------------------
/templates/account/email/password_changed_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% autoescape off %}
3 | {% blocktrans %}Password Changed{% endblocktrans %}
4 | {% endautoescape %}
5 |
--------------------------------------------------------------------------------
/templates/account/email/unknown_account_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% autoescape off %}
3 | {% blocktrans %}Unknown Account{% endblocktrans %}
4 | {% endautoescape %}
5 |
--------------------------------------------------------------------------------
/templates/account/messages/cannot_delete_primary_email.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% blocktrans %}You cannot remove your primary email address ({{email}}).{% endblocktrans %}
3 |
--------------------------------------------------------------------------------
/templates/mfa/email/webauthn_removed_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% autoescape off %}
3 | {% blocktrans %}Security Key Removed{% endblocktrans %}
4 | {% endautoescape %}
5 |
--------------------------------------------------------------------------------
/templates/account/email/password_reset_code_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% autoescape off %}
3 | {% blocktrans %}Password Reset Code{% endblocktrans %}
4 | {% endautoescape %}
5 |
--------------------------------------------------------------------------------
/templates/account/email/password_reset_key_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% autoescape off %}
3 | {% blocktrans %}Password Reset Email{% endblocktrans %}
4 | {% endautoescape %}
5 |
--------------------------------------------------------------------------------
/templates/allauth/elements/badge.html:
--------------------------------------------------------------------------------
1 | {% load allauth %}
2 |
3 | {% slot %}
4 | {% endslot %}
5 |
6 |
--------------------------------------------------------------------------------
/templates/mfa/email/totp_activated_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% autoescape off %}
3 | {% blocktrans %}Authenticator App Activated{% endblocktrans %}
4 | {% endautoescape %}
5 |
--------------------------------------------------------------------------------
/templates/mfa/email/totp_deactivated_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% autoescape off %}
3 | {% blocktrans %}Authenticator App Deactivated{% endblocktrans %}
4 | {% endautoescape %}
5 |
--------------------------------------------------------------------------------
/templates/mfa/totp/base.html:
--------------------------------------------------------------------------------
1 | {% extends "mfa/base_manage.html" %}
2 | {% load i18n %}
3 | {% block head_title %}
4 | {% trans "Authenticator App" %}
5 | {% endblock head_title %}
6 |
--------------------------------------------------------------------------------
/templates/mfa/webauthn/base.html:
--------------------------------------------------------------------------------
1 | {% extends "mfa/base_manage.html" %}
2 | {% load i18n %}
3 | {% block head_title %}
4 | {% trans "Security Keys" %}
5 | {% endblock head_title %}
6 |
--------------------------------------------------------------------------------
/templates/socialaccount/messages/account_connected_other.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% blocktrans %}The third-party account is already connected to a different account.{% endblocktrans %}
3 |
--------------------------------------------------------------------------------
/templates/account/email/account_already_exists_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% autoescape off %}
3 | {% blocktrans %}Account Already Exists{% endblocktrans %}
4 | {% endautoescape %}
5 |
--------------------------------------------------------------------------------
/templates/mfa/email/recovery_codes_generated_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% autoescape off %}
3 | {% blocktrans %}New Recovery Codes Generated{% endblocktrans %}
4 | {% endautoescape %}
5 |
--------------------------------------------------------------------------------
/templates/mfa/recovery_codes/base.html:
--------------------------------------------------------------------------------
1 | {% extends "mfa/base_manage.html" %}
2 | {% load i18n %}
3 | {% block head_title %}
4 | {% trans "Recovery Codes" %}
5 | {% endblock head_title %}
6 |
--------------------------------------------------------------------------------
/volunteer/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class VolunteerConfig(AppConfig):
5 | default_auto_field = "django.db.models.BigAutoField"
6 | name = "volunteer"
7 |
--------------------------------------------------------------------------------
/requirements-docs.txt:
--------------------------------------------------------------------------------
1 | mkdocs==1.6.0
2 | mkdocs-awesome-nav==3.1.1
3 | mkdocs-material[imaging]
4 | mkdocs-rss-plugin
5 | mkdocs-git-revision-date-localized-plugin
6 | mkdocs-git-committers-plugin-2
--------------------------------------------------------------------------------
/templates/account/email/email_confirmation_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% autoescape off %}
3 | {% blocktrans %}Please Confirm Your Email Address{% endblocktrans %}
4 | {% endautoescape %}
5 |
--------------------------------------------------------------------------------
/templates/account/messages/logged_in.txt:
--------------------------------------------------------------------------------
1 | {% load account %}
2 | {% load i18n %}
3 | {% user_display user as name %}
4 | {% blocktrans %}Successfully signed in as {{name}}.{% endblocktrans %}
5 |
--------------------------------------------------------------------------------
/templates/socialaccount/email/account_connected_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% autoescape off %}
3 | {% blocktrans %}Third-Party Account Connected{% endblocktrans %}
4 | {% endautoescape %}
5 |
--------------------------------------------------------------------------------
/templates/account/messages/email_confirmation_failed.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% blocktrans %}Unable to confirm {{email}} because it is already confirmed by a different account.{% endblocktrans %}
3 |
--------------------------------------------------------------------------------
/templates/socialaccount/email/account_disconnected_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% autoescape off %}
3 | {% blocktrans %}Third-Party Account Disconnected{% endblocktrans %}
4 | {% endautoescape %}
5 |
--------------------------------------------------------------------------------
/portal_account/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class PortalAccountConfig(AppConfig):
5 | default_auto_field = "django.db.models.BigAutoField"
6 | name = "portal_account"
7 |
--------------------------------------------------------------------------------
/templates/allauth/elements/form.html:
--------------------------------------------------------------------------------
1 | {% load allauth %}
2 |
8 |
--------------------------------------------------------------------------------
/storage_backend/custom_storage.py:
--------------------------------------------------------------------------------
1 | from storages.backends.s3boto3 import S3Boto3Storage
2 |
3 |
4 | class MediaStorage(S3Boto3Storage):
5 | """Media Storage files"""
6 |
7 | location = "media"
8 | file_overwrite = False
9 |
--------------------------------------------------------------------------------
/templates/account/email/password_set_message.txt:
--------------------------------------------------------------------------------
1 | {% extends "account/email/base_notification.txt" %}
2 | {% load i18n %}
3 |
4 | {% block notification_message %}{% blocktrans %}Your password has been set.{% endblocktrans %}{% endblock notification_message %}
5 |
--------------------------------------------------------------------------------
/templates/mfa/email/totp_activated_message.txt:
--------------------------------------------------------------------------------
1 | {% extends "account/email/base_notification.txt" %}
2 | {% load i18n %}
3 |
4 | {% block notification_message %}{% blocktrans %}Authenticator app activated.{% endblocktrans %}{% endblock notification_message %}
5 |
--------------------------------------------------------------------------------
/templates/account/email/email_confirm_message.txt:
--------------------------------------------------------------------------------
1 | {% extends "account/email/base_notification.txt" %}
2 | {% load i18n %}
3 |
4 | {% block notification_message %}{% blocktrans %}Your email has been confirmed.{% endblocktrans %}{% endblock notification_message %}
5 |
--------------------------------------------------------------------------------
/templates/account/email/password_reset_message.txt:
--------------------------------------------------------------------------------
1 | {% extends "account/email/base_notification.txt" %}
2 | {% load i18n %}
3 |
4 | {% block notification_message %}{% blocktrans %}Your password has been reset.{% endblocktrans %}{% endblock notification_message %}
5 |
--------------------------------------------------------------------------------
/templates/mfa/email/totp_deactivated_message.txt:
--------------------------------------------------------------------------------
1 | {% extends "account/email/base_notification.txt" %}
2 | {% load i18n %}
3 |
4 | {% block notification_message %}{% blocktrans %}Authenticator app deactivated.{% endblocktrans %}{% endblock notification_message %}
5 |
--------------------------------------------------------------------------------
/templates/mfa/email/webauthn_added_message.txt:
--------------------------------------------------------------------------------
1 | {% extends "account/email/base_notification.txt" %}
2 | {% load i18n %}
3 |
4 | {% block notification_message %}{% blocktrans %}A new security key has been added.{% endblocktrans %}{% endblock notification_message %}
5 |
--------------------------------------------------------------------------------
/templates/mfa/email/webauthn_removed_message.txt:
--------------------------------------------------------------------------------
1 | {% extends "account/email/base_notification.txt" %}
2 | {% load i18n %}
3 |
4 | {% block notification_message %}{% blocktrans %}A security key has been removed.{% endblocktrans %}{% endblock notification_message %}
5 |
--------------------------------------------------------------------------------
/templates/account/email/password_changed_message.txt:
--------------------------------------------------------------------------------
1 | {% extends "account/email/base_notification.txt" %}
2 | {% load i18n %}
3 |
4 | {% block notification_message %}{% blocktrans %}Your password has been changed.{% endblocktrans %}{% endblock notification_message %}
5 |
--------------------------------------------------------------------------------
/webhooks/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from . import views
4 |
5 | app_name = "webhooks"
6 |
7 | urlpatterns = [
8 | path(
9 | "pretix/",
10 | views.pretix_webhook,
11 | name="pretix_webhook",
12 | ),
13 | ]
14 |
--------------------------------------------------------------------------------
/docs/user/sponsor.md:
--------------------------------------------------------------------------------
1 | # Sponsors
2 |
3 | Thank you for your interest in sponsoring PyLadiesCon.
4 |
5 | First of all, please review all the available [sponsorship tiers](https://2025.conference.pyladies.com/en/sponsors/).
6 |
7 | The rest is TBD.
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/sponsorship/templates/sponsorship/email/team_status_notification.txt:
--------------------------------------------------------------------------------
1 | Hello team,
2 |
3 | The sponsorship profile for {{ profile.organization_name }} has been approved.
4 |
5 | Please review the profile and begin the sponsor onboarding process.
6 |
7 | Thanks,
8 | The System
9 |
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | -r requirements-app.txt
2 | black==25.1.0
3 | coverage==7.7.0
4 | django-debug-toolbar==5.0.1
5 | djlint==1.36.4
6 | flake8==7.2.0
7 | isort==6.0.1
8 | pip-audit==2.8.0
9 | pytest-django==4.8.0
10 | pytest==8.3.5
11 | pytest-cov==6.1.1
12 | coverage==7.7.0
13 |
--------------------------------------------------------------------------------
/templates/account/email/email_changed_message.txt:
--------------------------------------------------------------------------------
1 | {% extends "account/email/base_notification.txt" %}
2 | {% load i18n %}
3 |
4 | {% block notification_message %}{% blocktrans %}Your email has been changed from {{ from_email }} to {{ to_email }}.{% endblocktrans %}{% endblock notification_message %}
5 |
--------------------------------------------------------------------------------
/templates/account/email/email_deleted_message.txt:
--------------------------------------------------------------------------------
1 | {% extends "account/email/base_notification.txt" %}
2 | {% load i18n %}
3 |
4 | {% block notification_message %}{% blocktrans %}Email address {{ deleted_email }} has been removed from your account.{% endblocktrans %}{% endblock notification_message %}
5 |
--------------------------------------------------------------------------------
/sponsorship/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class SponsorshipConfig(AppConfig):
5 | default_auto_field = "django.db.models.BigAutoField"
6 | name = "sponsorship"
7 |
8 | def ready(self):
9 | import sponsorship.signals # noqa: this registers the signals
10 |
--------------------------------------------------------------------------------
/templates/mfa/email/recovery_codes_generated_message.txt:
--------------------------------------------------------------------------------
1 | {% extends "account/email/base_notification.txt" %}
2 | {% load i18n %}
3 |
4 | {% block notification_message %}{% blocktrans %}A new set of Two-Factor Authentication recovery codes has been generated.{% endblocktrans %}{% endblock notification_message %}
5 |
--------------------------------------------------------------------------------
/templates/account/snippets/warn_no_email.html:
--------------------------------------------------------------------------------
1 | {% load i18n allauth %}
2 | {% element p %}
3 | {% trans "Warning:" %} {% trans "You currently do not have any email address set up. You should really add an email address so you can receive notifications, reset your password, etc." %}
4 | {% endelement %}
5 |
--------------------------------------------------------------------------------
/templates/socialaccount/email/account_connected_message.txt:
--------------------------------------------------------------------------------
1 | {% extends "account/email/base_notification.txt" %}
2 | {% load i18n %}
3 |
4 | {% block notification_message %}{% blocktrans %}A third-party account from {{ provider }} has been connected to your account.{% endblocktrans %}{% endblock notification_message %}
5 |
--------------------------------------------------------------------------------
/templates/socialaccount/email/account_disconnected_message.txt:
--------------------------------------------------------------------------------
1 | {% extends "account/email/base_notification.txt" %}
2 | {% load i18n %}
3 |
4 | {% block notification_message %}{% blocktrans %}A third-party account from {{ provider }} has been disconnected from your account.{% endblocktrans %}{% endblock notification_message %}
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guide
2 |
3 | All forms of contributions are welcome and appreciated. We have many contribution opportunities, including code, testing, and documentations.
4 |
5 | See the [Contributing section](https://pyladiescon-portal-docs.netlify.app/developer/contributing/) of the Developer Guide for more specifics.
6 |
--------------------------------------------------------------------------------
/templates/mfa/webauthn/snippets/scripts.html:
--------------------------------------------------------------------------------
1 | {% load i18n static %}
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sponsorship/templates/sponsorship/email/sponsor_status_update.txt:
--------------------------------------------------------------------------------
1 | Hi {{ profile.user.first_name }},
2 |
3 | Thank you for submitting your sponsorship application for {{ profile.organization_name }}!
4 |
5 | We’ve received your profile and our team will review it shortly. You will receive a follow-up email once a decision has been made.
6 |
7 | Best,
8 | The PyLadiesCon Team
9 |
--------------------------------------------------------------------------------
/templates/emails/sponsorship/internal_sponsor_onboarding.md:
--------------------------------------------------------------------------------
1 | {% extends "emails/base_email.md" %}
2 | {% load i18n %}
3 | {% block content %}
4 |
5 | Hello, {{ recipient_name }}. We're writing to let you know that a **new Sponsorship has been added** to the system.
6 |
7 | {% include "emails/sponsorship/sponsor_information_partial.md" with profile=profile %}
8 |
9 | {% endblock content %}
--------------------------------------------------------------------------------
/sponsorship/templates/sponsorship/email/sponsor_approved.txt:
--------------------------------------------------------------------------------
1 | Hi {{ profile.user.first_name }},
2 |
3 | Congratulations! Your sponsorship application for {{ profile.organization_name }} has been approved 🎉
4 |
5 | We're excited to have you as a sponsor for PyLadiesCon. A team member will reach out soon with next steps and onboarding information.
6 |
7 | Best,
8 | The PyLadiesCon Team
9 |
--------------------------------------------------------------------------------
/config/gunicorn.conf.py:
--------------------------------------------------------------------------------
1 | bind = "unix:/var/run/cabotage/cabotage.sock"
2 | backlog = 2048
3 | preload_app = True
4 | max_requests = 2048
5 | max_requests_jitter = 128
6 |
7 | worker_connections = 1000
8 | timeout = 60
9 | keepalive = 2
10 |
11 | errorlog = "-"
12 | loglevel = "info"
13 | accesslog = "-"
14 |
15 |
16 | def when_ready(server):
17 | open("/tmp/app-initialized", "w").close()
18 |
--------------------------------------------------------------------------------
/templates/emails/sponsorship/internal_sponsor_updated.md:
--------------------------------------------------------------------------------
1 | {% extends "emails/base_email.md" %}
2 | {% load i18n %}
3 | {% block content %}
4 |
5 | There has been an **update to the sponsorship profile** for {{ profile.organization_name }}.
6 |
7 | Please review the changes.
8 |
9 | {% include "emails/sponsorship/sponsor_information_partial.md" with profile=profile %}
10 |
11 | {% endblock content %}
--------------------------------------------------------------------------------
/templates/account/email/base_message.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}{% autoescape off %}{% blocktrans with site_name=current_site.name %}Hello from {{ site_name }}!{% endblocktrans %}
2 |
3 | {% block content %}{% endblock content %}
4 |
5 | {% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Thank you for using {{ site_name }}!
6 | {{ site_domain }}{% endblocktrans %}
7 | {% endautoescape %}
8 |
--------------------------------------------------------------------------------
/templates/account/snippets/already_logged_in.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% load account %}
3 | {% load allauth %}
4 | {% user_display user as user_display %}
5 | {% element alert %}
6 | {% slot message %}
7 | {% blocktranslate %}Note{% endblocktranslate %}: {% blocktranslate %}You are already logged in as {{ user_display }}.{% endblocktranslate %}
8 | {% endslot %}
9 | {% endelement %}
10 |
--------------------------------------------------------------------------------
/sponsorship/templates/sponsorship/create_profile.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block content %}
3 |
4 | Create Sponsorship Profile
5 |
6 |
13 | {% endblock content %}
14 |
--------------------------------------------------------------------------------
/docs/user/core_team.md:
--------------------------------------------------------------------------------
1 | # Core Team
2 |
3 | As part of PyLadiesCon Core team, you'll have access to additional features and the django admin area.
4 |
5 | ## Onboarding
6 |
7 | 1. Create an account and verify it.
8 | 2. Notify the tech lead or other existing core team members so that they can add you as a superuser
9 |
10 | ## Core team features
11 |
12 | - Access to the Django admin area
13 | - TBD
14 |
15 |
16 |
--------------------------------------------------------------------------------
/templates/allauth/elements/panel.html:
--------------------------------------------------------------------------------
1 | {% load allauth %}
2 |
3 |
4 | {% slot title %}
5 | {% endslot %}
6 |
7 | {% slot body %}
8 | {% endslot %}
9 | {% if slots.actions %}
10 |
25 | other attendees for three days of learning, inspiration, and connection.
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/templates/socialaccount/login.html:
--------------------------------------------------------------------------------
1 | {% extends "socialaccount/base_entrance.html" %}
2 | {% load i18n %}
3 | {% load allauth %}
4 | {% block head_title %}
5 | {% trans "Sign In" %}
6 | {% endblock head_title %}
7 | {% block content %}
8 | {% if process == "connect" %}
9 | {% element h1 %}
10 | {% blocktrans with provider.name as provider %}Connect {{ provider }}{% endblocktrans %}
11 | {% endelement %}
12 | {% element p %}
13 | {% blocktrans with provider.name as provider %}You are about to connect a new third-party account from {{ provider }}.{% endblocktrans %}
14 | {% endelement %}
15 | {% else %}
16 | {% element h1 %}
17 | {% blocktrans with provider.name as provider %}Sign In Via {{ provider }}{% endblocktrans %}
18 | {% endelement %}
19 | {% element p %}
20 | {% blocktrans with provider.name as provider %}You are about to sign in using a third-party account from {{ provider }}.{% endblocktrans %}
21 | {% endelement %}
22 | {% endif %}
23 | {% element form method="post" no_visible_fields=True %}
24 | {% slot actions %}
25 | {% csrf_token %}
26 | {% element button type="submit" %}
27 | {% trans "Continue" %}
28 | {% endelement %}
29 | {% endslot %}
30 | {% endelement %}
31 | {% endblock content %}
32 |
--------------------------------------------------------------------------------
/common/mixins.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.mixins import UserPassesTestMixin
2 |
3 | from volunteer.models import VolunteerProfile
4 |
5 |
6 | class AdminRequiredMixin(UserPassesTestMixin):
7 | """Mixin for views that require administrative permission.
8 |
9 | Currently it requires the user to be a superuser or staff member.
10 | This can be extended to include more complex permission checks in the future.
11 | """
12 |
13 | def test_func(self):
14 | return self.request.user.is_superuser or self.request.user.is_staff
15 |
16 |
17 | class VolunteerOrAdminRequiredMixin(UserPassesTestMixin):
18 | """Mixin for views that require the user to be the owner of the object or an admin.
19 |
20 | This is useful for views where users can only access their own data unless they have
21 | administrative privileges.
22 | """
23 |
24 | def test_func(self):
25 | pk = self.kwargs.get("pk")
26 | instance = VolunteerProfile.objects.filter(pk=pk).first()
27 | is_owner = False
28 | if instance and instance.user == self.request.user:
29 | is_owner = True
30 | is_admin = self.request.user.is_superuser or self.request.user.is_staff
31 | return is_owner or is_admin
32 |
--------------------------------------------------------------------------------
/templates/account/signup_by_passkey.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base_entrance.html" %}
2 | {% load allauth i18n %}
3 | {% block head_title %}
4 | {% trans "Signup" %}
5 | {% endblock head_title %}
6 | {% block content %}
7 | {% element h1 %}
8 | {% trans "Passkey Sign Up" %}
9 | {% endelement %}
10 | {% setvar link %}
11 |
12 | {% endsetvar %}
13 | {% setvar end_link %}
14 |
15 | {% endsetvar %}
16 | {% element p %}
17 | {% blocktranslate %}Already have an account? Then please {{ link }}sign in{{ end_link }}.{% endblocktranslate %}
18 | {% endelement %}
19 | {% url 'account_signup_by_passkey' as action_url %}
20 | {% element form form=form method="post" action=action_url tags="entrance,signup" %}
21 | {% slot body %}
22 | {% csrf_token %}
23 | {% element fields form=form unlabeled=True %}
24 | {% endelement %}
25 | {{ redirect_field }}
26 | {% endslot %}
27 | {% slot actions %}
28 | {% element button tags="prominent,signup" type="submit" %}
29 | {% trans "Sign Up" %}
30 | {% endelement %}
31 | {% endslot %}
32 | {% endelement %}
33 | {% element hr %}
34 | {% endelement %}
35 | {% element button href=signup_url tags="prominent,signup,outline,primary" %}
36 | {% trans "Other options" %}
37 | {% endelement %}
38 | {% endblock content %}
39 |
--------------------------------------------------------------------------------
/templates/portal/sponsor_donate_callout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Support PyLadiesCon!
7 |
8 |
9 | Help us make PyLadiesCon possible by becoming a sponsor or making a donation.
10 | Your support enables us to create an inclusive and accessible conference for the global PyLadies
11 | community.
12 |
30 |
--------------------------------------------------------------------------------
/templates/mfa/recovery_codes/index.html:
--------------------------------------------------------------------------------
1 | {% extends "mfa/recovery_codes/base.html" %}
2 | {% load i18n %}
3 | {% load allauth %}
4 | {% block content %}
5 | {% element h1 %}
6 | {% translate "Recovery Codes" %}
7 | {% endelement %}
8 | {% element p %}
9 | {% blocktranslate count unused_count=unused_codes|length %}There is {{ unused_count }} out of {{ total_count }} recovery codes available.{% plural %}There are {{ unused_count }} out of {{ total_count }} recovery codes available.{% endblocktranslate %}
10 | {% endelement %}
11 | {% element field id="recovery_codes" type="textarea" disabled=True rows=unused_codes|length readonly=True %}
12 | {% slot label %}
13 | {% translate "Unused codes" %}
14 | {% endslot %}
15 | {% comment %} djlint:off {% endcomment %}
16 | {% slot value %}{% for code in unused_codes %}{% if forloop.counter0 %}
17 | {% endif %}{{ code }}{% endfor %}{% endslot %}
18 | {% comment %} djlint:on {% endcomment %}
19 | {% endelement %}
20 | {% if unused_codes %}
21 | {% url 'mfa_download_recovery_codes' as download_url %}
22 | {% element button href=download_url %}
23 | {% translate "Download codes" %}
24 | {% endelement %}
25 | {% endif %}
26 | {% url 'mfa_generate_recovery_codes' as generate_url %}
27 | {% element button href=generate_url %}
28 | {% translate "Generate new codes" %}
29 | {% endelement %}
30 | {% endblock content %}
31 |
--------------------------------------------------------------------------------
/docs/user/get_started.md:
--------------------------------------------------------------------------------
1 | # Getting Started with PyLadiesCon
2 |
3 | ## Who needs an account?
4 |
5 | If you're interested in volunteering or sponsoring the conference, you'll need to create an account.
6 |
7 | If you're part of the PyLadiesCon Core team, you'll also need to create an account. This will allow you to access the Django admin area and other features.
8 |
9 | If you're a speaker, at this time you don't need to create an account yet. (TBD)
10 |
11 | If you're a conference attendee, you don't need to create an account. (TBD)
12 |
13 | ## Sign up for an account
14 |
15 | In order to volunteer with PyLadiesCon, you'll first need to create an account.
16 |
17 | 1. Go to the [Sign up page](https://portal.pyladies.com/accounts/signup/)
18 | 2. Choose a username, enter your email address, and your password.
19 |
20 | ## Verify your account
21 |
22 | After signing up, you'll receive an email with a verification code. Enter this code on the sign up page to verify your
23 | account.
24 |
25 | ## Changing your email address
26 |
27 | You can add more than one email address to your account. We will be sending emails to your primary account.
28 |
29 | To change an email address, add a new one to your account, and then set it as the primary email address.
30 |
31 | We will only send email to your secondary email if we've been unable to contact you in your primary email address.
32 |
33 |
--------------------------------------------------------------------------------
/docs/developer/contributing.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Contributing
3 | description: Contributing to PyLadiesCon Portal
4 | ---
5 |
6 | # Contributing Guide
7 |
8 | ## Where and How to get started
9 |
10 | Find an issue in our [repo](https://github.com/pyladies/pyladiescon-portal) to get started. You can also create a new issue or [start a discussion item](https://github.com/pyladies/pyladiescon-portal/discussions) on GitHub.
11 |
12 | ## Setup
13 |
14 | To start working with the code, refer to our [setup guide](/developer/setup/).
15 |
16 | Ask questions in the **[#portal_dev](https://discord.gg/X6fcufjb)** channel of our Discord comunity.
17 |
18 | ## Running Tests
19 |
20 | Tests are run using [pytest](https://docs.pytest.org/en/latest/).
21 |
22 | To run the tests:
23 |
24 | ```bash
25 | make test
26 | ```
27 |
28 | ## Test coverage
29 |
30 | We aim for 100% test coverage. The coverage report is generated after running the tests, and can be viewed in the `htmlcov` directory.
31 |
32 | ## Code style and linting
33 |
34 | Run ``make reformat`` and ``make check`` prior to committing your code.
35 | There is a CI that checks for code style and linting issues.
36 |
37 | PRs will not be merged if there is any CI failures.
38 |
39 | ## Documentation Preview on Pull Requests
40 |
41 | A documentation preview is generated for each pull request using Netlify. This allows us to preview
42 | the docs changes before merging the PR.
43 |
--------------------------------------------------------------------------------
/portal/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.1.7 on 2025-03-18 22:57
2 |
3 | import django.utils.timezone
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = []
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name="BaseModel",
16 | fields=[
17 | (
18 | "id",
19 | models.BigAutoField(
20 | auto_created=True,
21 | primary_key=True,
22 | serialize=False,
23 | verbose_name="ID",
24 | ),
25 | ),
26 | (
27 | "creation_date",
28 | models.DateTimeField(
29 | default=django.utils.timezone.now,
30 | editable=False,
31 | verbose_name="creation_date",
32 | ),
33 | ),
34 | (
35 | "modified_date",
36 | models.DateTimeField(
37 | default=django.utils.timezone.now,
38 | editable=False,
39 | verbose_name="modified_date",
40 | ),
41 | ),
42 | ],
43 | ),
44 | ]
45 |
--------------------------------------------------------------------------------
/portal/models.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.contrib.admin.widgets import FilteredSelectMultiple
3 | from django.contrib.postgres.fields import ArrayField
4 | from django.db import models
5 | from django.utils.timezone import now
6 |
7 |
8 | class ChoiceArrayField(ArrayField):
9 |
10 | def formfield(self, **kwargs): # pragma: no cover
11 | # Currently this is only used the languages_spoken field but the field is being deprecated
12 | defaults = {
13 | "form_class": forms.TypedMultipleChoiceField,
14 | "choices": self.base_field.choices,
15 | "coerce": self.base_field.to_python,
16 | "widget": FilteredSelectMultiple(self.verbose_name, False),
17 | }
18 | defaults.update(kwargs)
19 | return super(ArrayField, self).formfield(**defaults)
20 |
21 |
22 | class BaseModel(models.Model):
23 | creation_date = models.DateTimeField(
24 | "creation_date", editable=False, auto_now_add=True
25 | )
26 | modified_date = models.DateTimeField("modified_date", editable=False, auto_now=True)
27 |
28 | def save(self, *args, **kwargs):
29 | self.modified_date = now()
30 | if (
31 | "update_fields" in kwargs and "modified_date" not in kwargs["update_fields"]
32 | ): # pragma: no cover
33 | kwargs["update_fields"].append("modified_date")
34 | super().save(*args, **kwargs)
35 |
--------------------------------------------------------------------------------
/docs/about/index.md:
--------------------------------------------------------------------------------
1 | ## Why this project exists
2 |
3 | Being an online, multi-language, multi-timezone conference, we face unique and different challenges from other types of events and conferences.
4 | Our organizers are all volunteers from different part of the world.
5 | We have many communications and coordinations with our team of volunteers ahead of the conference, and less during the conference itself.
6 |
7 | ## Challenges in managing our online conference
8 |
9 | 🤕 Our organizing team collaborate with each other to manage our volunteers, our speakers, and our sponsors.
10 | Each team also collaborate with our design and media team to produce promotional assets.
11 | For the last 2 years, we manage a lot of our assets and information using spreadsheets and Google Forms.
12 | However, managing and sharing data with various volunteers using spreadsheets have been challenging, and causing frustrations and confusions among our team of volunteers.
13 |
14 | ## What we're building: PyLadiesCon Web Portal
15 |
16 | 💻 This year, we are developing an online web portal for us to manage the behind the scenes work of our conference.
17 | Instead of spreadsheets, we will be accepting volunteer sign ups and sponsorship sign ups through our web portal.
18 | Our team leads will be assigning task and tracking team progress through the web portal.
19 | We also want to build a conference dashboard to provide overview and statistics about our conference.
20 |
--------------------------------------------------------------------------------
/templates/mfa/totp/activate_form.html:
--------------------------------------------------------------------------------
1 | {% extends "mfa/totp/base.html" %}
2 | {% load allauth i18n %}
3 | {% block head_title %}
4 | {% translate "Activate Authenticator App" %}
5 | {% endblock head_title %}
6 | {% block content %}
7 | {% element h1 %}
8 | {% translate "Activate Authenticator App" %}
9 | {% endelement %}
10 | {% element p %}
11 | {% blocktranslate %}To protect your account with two-factor authentication, scan the QR code below with your authenticator app. Then, input the verification code generated by the app below.{% endblocktranslate %}
12 | {% endelement %}
13 | {% url 'mfa_activate_totp' as action_url %}
14 | {% element form form=form method="post" action=action_url %}
15 | {% slot body %}
16 | {% element img src=totp_svg_data_uri alt=form.secret tags="mfa,totp,qr" %}
17 | {% endelement %}
18 | {% csrf_token %}
19 | {% element field id="authenticator_secret" type="text" value=form.secret disabled=True %}
20 | {% slot label %}
21 | {% translate "Authenticator secret" %}
22 | {% endslot %}
23 | {% slot help_text %}
24 | {% translate "You can store this secret and use it to reinstall your authenticator app at a later time." %}
25 | {% endslot %}
26 | {% endelement %}
27 | {% element fields form=form %}
28 | {% endelement %}
29 | {% endslot %}
30 | {% slot actions %}
31 | {% element button type="submit" %}
32 | {% trans "Activate" %}
33 | {% endelement %}
34 | {% endslot %}
35 | {% endelement %}
36 | {% endblock content %}
37 |
--------------------------------------------------------------------------------
/templates/portal_account/portalprofile_edit.html:
--------------------------------------------------------------------------------
1 | {% extends "portal/base.html" %}
2 | {% load allauth i18n %}
3 | {% load django_bootstrap5 %}
4 | {% block body %}
5 | {% endblock body %}
6 | {% block content %}
7 |
8 |
9 | {% trans "Edit your Portal Profile" %}
10 |
11 |
12 | {% trans "Update your portal profile information below." %}
13 |
14 |
15 | {% bootstrap_form_errors form %}
16 |
32 |
33 |
34 | {% endblock content %}
35 |
--------------------------------------------------------------------------------
/templates/emails/volunteer/internal_volunteer_profile_email_notification.md:
--------------------------------------------------------------------------------
1 | {% extends "emails/base_email.md" %}
2 | {% load i18n %}
3 | {% block content %}
4 |
5 | Hello, {{ recipient_name }}.
6 |
7 | We're writing to let you know that a new volunteer has just signed up. Be sure to review their application in a timely manner.
8 |
9 | **Volunteer Application Status:** {{ profile.application_status }}.
10 |
11 | ## Volunteer Information
12 |
13 | **Name:** {{ profile.user.first_name }} {{ profile.user.last_name }}
14 |
15 | ### Details
16 |
17 | - **Availability:** {{ profile.availability_hours_per_week }} hours per week
18 | - **GitHub username:** {{ profile.github_username }}
19 | - **Discord username:** {{ profile.discord_username }}
20 | - **Instagram username:** {{ profile.instagram_username }}
21 | - **Bluesky username:** {{ profile.bluesky_username }}
22 | - **Mastodon URL:** {{ profile.mastodon_url }}
23 | - **X username:** {{ profile.x_username }}
24 | - **LinkedIn URL:** {{ profile.linkedin_url }}
25 | - **PyLadies Chapter:** {{ profile.chapter }}
26 | - **Region:** {{ profile.region }}
27 | - **Additional Comments:** {{ profile.additional_comments }}
28 |
29 | To approve this volunteer and assign them to your teams, click [here](https://{{ current_site.domain }}{% url 'volunteer:volunteer_profile_manage' profile.id %}).
30 |
31 | To manage other volunteers, visit the [Volunteer Management Portal](https://{{ current_site.domain }}{% url 'volunteer:volunteer_profile_list' %}).
32 |
33 | {% endblock content %}
--------------------------------------------------------------------------------
/templates/emails/volunteer/volunteer_cancellation_confirmation.md:
--------------------------------------------------------------------------------
1 | {% extends "emails/base_email.md" %}
2 | {% load i18n %}
3 | {% block content %}
4 |
5 | Dear {{ profile.user.first_name|default:profile.user.username }},
6 |
7 | This email confirms that your Volunteer application for PyLadiesCon has been cancelled.
8 |
9 | ## Changes Made
10 |
11 | Your Volunteer Profile status has been set to **"Cancelled"**
12 |
13 | {% if teams_removed %}
14 | You have been removed from the following teams:
15 |
16 | {% for team in teams_removed %}- {{ team.short_name }} {% endfor %}
17 |
18 | {% endif %}
19 |
20 | {% if roles_removed %}
21 | You have been removed from the following roles:
22 |
23 | {% for role in roles_removed %}- {{ role.short_name }} {% endfor %}
24 |
25 | {% endif %}
26 |
27 | Team leads have been notified of your departure.
28 |
29 | Your access to volunteer resources will be revoked within the next 24 hours.
30 |
31 | We understand that circumstances can change, and we appreciate the time you were willing to dedicate to PyLadiesCon.
32 |
33 | If you change your mind in the future and would like to volunteer again,
34 | you're welcome to submit a new volunteer application through our portal.
35 |
36 | If this was a mistake, or you do not wish to cancel your application, please contact us so that we
37 | can rectify the situation.
38 |
39 | Thank you for your interest in PyLadiesCon, and we hope to see you as a participant or volunteer in future events!
40 |
41 | Best regards.
42 |
43 | {% endblock content %}
--------------------------------------------------------------------------------
/templates/portal/historical_comparison_charts.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Historical comparison charts for PyLadiesCon stats across years
3 | Uses Google Charts to display bar charts comparing metrics from 2023, 2024, and 2025
4 | {% endcomment %}
5 | {% for comparison in stats.historical_comparison %}
6 | var data_{{ comparison.chart_id }} = new google.visualization.DataTable();
7 | {% for column in comparison.columns %}
8 | data_{{ comparison.chart_id }}.addColumn('{{ column.0 }}', '{{ column.1 }}');
9 | {% endfor %}
10 | data_{{ comparison.chart_id }}.addRows([
11 | {% for row in comparison.data %}
12 | ['{{ row.0 }}', {{ row.1 }}]
13 | {% if not forloop.last %}
14 | ,
15 | {% endif %}
16 | {% endfor %}
17 | ]);
18 | var options_{{ comparison.chart_id }} = {
19 | title: '{{ comparison.title }}',
20 | chartArea: { width: '70%' },
21 | hAxis: {
22 | title: 'Year',
23 | minValue: 0
24 | },
25 | vAxis: {
26 | title: '{{ comparison.columns.1.1 }}'
27 | },
28 | colors: ['#7C5BC8'],
29 | legend: { position: 'none' },
30 | animation: {
31 | startup: true,
32 | duration: 1000,
33 | easing: 'out'
34 | }
35 | };
36 | var chart_{{ comparison.chart_id }} = new google.visualization.ColumnChart(
37 | document.getElementById('{{ comparison.chart_id }}')
38 | );
39 | chart_{{ comparison.chart_id }}.draw(data_{{ comparison.chart_id }}, options_{{ comparison.chart_id }});
40 | {% endfor %}
41 |
--------------------------------------------------------------------------------
/sponsorship/migrations/0004_sponsorshipprofile_organization_address_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.2.7 on 2025-10-26 04:23
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("sponsorship", "0003_remove_sponsorshipprofile_application_status_and_more"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="sponsorshipprofile",
15 | name="organization_address",
16 | field=models.TextField(blank=True, null=True),
17 | ),
18 | migrations.AddField(
19 | model_name="sponsorshipprofile",
20 | name="sponsor_contact_name",
21 | field=models.CharField(blank=True, max_length=255, null=True),
22 | ),
23 | migrations.AddField(
24 | model_name="sponsorshipprofile",
25 | name="sponsors_contact_email",
26 | field=models.EmailField(blank=True, max_length=254, null=True),
27 | ),
28 | migrations.AddField(
29 | model_name="sponsorshipprofile",
30 | name="sponsorship_override_amount",
31 | field=models.DecimalField(
32 | blank=True, decimal_places=2, max_digits=10, null=True
33 | ),
34 | ),
35 | migrations.AlterField(
36 | model_name="sponsorshipprofile",
37 | name="company_description",
38 | field=models.TextField(blank=True, null=True),
39 | ),
40 | ]
41 |
--------------------------------------------------------------------------------
/templates/emails/volunteer/team_is_now_closed.md:
--------------------------------------------------------------------------------
1 | {% extends "emails/base_email.md" %}
2 | {% load i18n %}
3 | {% block content %}
4 |
5 | {{ profile.user.first_name }}, thank you for applying to volunteer with us.
6 |
7 | {% if updated %}
8 | Your volunteer profile has recently been updated.
9 | {% endif %}
10 |
11 | ## Application Status
12 |
13 | We received many volunteer applications, and we're not able to accept everyone. At this time, the team you've applied for is **at full capacity**.
14 |
15 | We are placing you on a **waitlist** in case the volunteer opportunity opens up in the future.
16 |
17 | Thank you for your understanding and for your interest in volunteering with us!
18 |
19 | ## Your Information
20 |
21 | - **Availability:** {{ profile.availability_hours_per_week }} hours per week
22 | - **GitHub username:** {{ profile.github_username }}
23 | - **Discord username:** {{ profile.discord_username }}
24 | - **Instagram username:** {{ profile.instagram_username }}
25 | - **Bluesky username:** {{ profile.bluesky_username }}
26 | - **Mastodon URL:** {{ profile.mastodon_url }}
27 | - **X username:** {{ profile.x_username }}
28 | - **LinkedIn URL:** {{ profile.linkedin_url }}
29 | - **PyLadies Chapter:** {{ profile.chapter }}
30 | - **Region:** {{ profile.region }}
31 | - **Additional Comments:** {{ profile.additional_comments }}
32 |
33 | If you would like to review or update your application at any time, go to your [Volunteer Dashboard](https://{{ current_site.domain }}{% url 'volunteer:index' %}).
34 |
35 | {% endblock content %}
--------------------------------------------------------------------------------
/portal/forms.py:
--------------------------------------------------------------------------------
1 | from allauth.account.forms import SignupForm
2 | from django import forms
3 |
4 |
5 | class CustomSignupForm(SignupForm):
6 | first_name = forms.CharField(
7 | max_length=200,
8 | label="First Name",
9 | widget=forms.TextInput(attrs={"placeholder": "First Name"}),
10 | )
11 | last_name = forms.CharField(
12 | max_length=200,
13 | label="Last Name",
14 | widget=forms.TextInput(attrs={"placeholder": "Last Name"}),
15 | )
16 | coc_agreement = forms.BooleanField(
17 | required=True,
18 | label="I agree to the Code of Conduct",
19 | help_text="You must agree to our Code of Conduct to use this site.",
20 | )
21 | tos_agreement = forms.BooleanField(
22 | required=True,
23 | label="I agree to the Terms of Service",
24 | help_text="You must agree to our Terms of Service to use this site.",
25 | )
26 |
27 | def save(self, request):
28 | user = super().save(request)
29 | user.first_name = self.cleaned_data["first_name"]
30 | user.last_name = self.cleaned_data["last_name"]
31 | user.save()
32 |
33 | from portal_account.models import PortalProfile
34 |
35 | portal_profile, created = PortalProfile.objects.get_or_create(user=user)
36 | portal_profile.coc_agreement = self.cleaned_data.get("coc_agreement", False)
37 | portal_profile.tos_agreement = self.cleaned_data.get("tos_agreement", False)
38 | portal_profile.save()
39 |
40 | return user
41 |
--------------------------------------------------------------------------------
/templates/account/email_confirm.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base_entrance.html" %}
2 | {% load i18n %}
3 | {% load account %}
4 | {% load allauth %}
5 | {% block head_title %}
6 | {% trans "Confirm Email Address" %}
7 | {% endblock head_title %}
8 | {% block content %}
9 | {% element h1 %}
10 | {% trans "Confirm Email Address" %}
11 | {% endelement %}
12 | {% if confirmation %}
13 | {% user_display confirmation.email_address.user as user_display %}
14 | {% if can_confirm %}
15 | {% element p %}
16 | {% blocktrans with confirmation.email_address.email as email %}Please confirm that {{ email }} is an email address for user {{ user_display }}.{% endblocktrans %}
17 | {% endelement %}
18 | {% url "account_confirm_email" confirmation.key as action_url %}
19 | {% element form method="post" action=action_url %}
20 | {% slot actions %}
21 | {% csrf_token %}
22 | {{ redirect_field }}
23 | {% element button type="submit" %}
24 | {% trans "Confirm" %}
25 | {% endelement %}
26 | {% endslot %}
27 | {% endelement %}
28 | {% else %}
29 | {% element p %}
30 | {% blocktrans %}Unable to confirm {{ email }} because it is already confirmed by a different account.{% endblocktrans %}
31 | {% endelement %}
32 | {% endif %}
33 | {% else %}
34 | {% url "account_email" as email_url %}
35 | {% element p %}
36 | {% blocktrans %}This email confirmation link expired or is invalid. Please issue a new email confirmation request.{% endblocktrans %}
37 | {% endelement %}
38 | {% endif %}
39 | {% endblock content %}
40 |
--------------------------------------------------------------------------------
/templates/emails/volunteer/volunteer_profile_email_notification.md:
--------------------------------------------------------------------------------
1 | {% extends "emails/base_email.md" %}
2 | {% load i18n %}
3 | {% block content %}
4 |
5 | {{ profile.user.first_name }}, thank you for applying to volunteer with us.
6 |
7 | {% if updated %}
8 | Your volunteer profile has recently been updated.
9 | {% endif %}
10 |
11 | ## Application Status
12 |
13 | Your current volunteer application status: **{{ profile.application_status }}**.
14 |
15 | ## Your Information
16 |
17 | - **Availability:** {{ profile.availability_hours_per_week }} hours per week
18 | - **GitHub username:** {{ profile.github_username }}
19 | - **Discord username:** {{ profile.discord_username }}
20 | - **Instagram username:** {{ profile.instagram_username }}
21 | - **Bluesky username:** {{ profile.bluesky_username }}
22 | - **Mastodon URL:** {{ profile.mastodon_url }}
23 | - **X username:** {{ profile.x_username }}
24 | - **LinkedIn URL:** {{ profile.linkedin_url }}
25 | - **PyLadies Chapter:** {{ profile.chapter }}
26 | - **Region:** {{ profile.region }}
27 | - **Additional Comments:** {{ profile.additional_comments }}
28 |
29 | If you would like to review or update your application at any time, go to your [Volunteer Dashboard](https://{{ current_site.domain }}{% url 'volunteer:index' %}) at https://{{ current_site.domain }}{% url 'volunteer:index' %}.
30 |
31 | Be sure to join our [Discord server](https://discord.com/invite/2fUN4ddVfP) at https://discord.com/invite/2fUN4ddVfP. All of our volunteers are required to join the server to communicate and collaborate with the rest of the team.
32 |
33 | {% endblock content %}
--------------------------------------------------------------------------------
/templates/account/password_reset_from_key.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base_entrance.html" %}
2 | {% load i18n %}
3 | {% load allauth %}
4 | {% block head_title %}
5 | {% trans "Change Password" %}
6 | {% endblock head_title %}
7 | {% block content %}
8 | {% element h1 %}
9 | {% if token_fail %}
10 | {% trans "Bad Token" %}
11 | {% else %}
12 | {% trans "Change Password" %}
13 | {% endif %}
14 | {% endelement %}
15 | {% if token_fail %}
16 | {% url 'account_reset_password' as passwd_reset_url %}
17 | {% element p %}
18 | {% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktrans %}
19 | {% endelement %}
20 | {% else %}
21 | {% element form method="post" action=action_url %}
22 | {% slot body %}
23 | {% csrf_token %}
24 | {{ redirect_field }}
25 | {% element fields form=form %}
26 | {% endelement %}
27 | {% endslot %}
28 | {% slot actions %}
29 | {% element button type="submit" name="action" %}
30 | {% trans "Change Password" %}
31 | {% endelement %}
32 | {% element button type="submit" form="logout-from-stage" tags="link,cancel" %}
33 | {% translate "Cancel" %}
34 | {% endelement %}
35 | {% endslot %}
36 | {% endelement %}
37 | {% endif %}
38 | {% if not cancel_url %}
39 |
45 | {% endif %}
46 | {% endblock content %}
47 |
--------------------------------------------------------------------------------
/portal_account/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.1.7 on 2025-03-23 21:45
2 |
3 | import django.db.models.deletion
4 | from django.conf import settings
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | ("portal", "0001_initial"),
14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
15 | ]
16 |
17 | operations = [
18 | migrations.CreateModel(
19 | name="PortalProfile",
20 | fields=[
21 | (
22 | "basemodel_ptr",
23 | models.OneToOneField(
24 | auto_created=True,
25 | on_delete=django.db.models.deletion.CASCADE,
26 | parent_link=True,
27 | primary_key=True,
28 | serialize=False,
29 | to="portal.basemodel",
30 | ),
31 | ),
32 | (
33 | "pronouns",
34 | models.CharField(blank=True, max_length=100, null=True),
35 | ),
36 | ("coc_agreement", models.BooleanField(default=False)),
37 | (
38 | "user",
39 | models.OneToOneField(
40 | on_delete=django.db.models.deletion.CASCADE,
41 | to=settings.AUTH_USER_MODEL,
42 | ),
43 | ),
44 | ],
45 | bases=("portal.basemodel",),
46 | ),
47 | ]
48 |
--------------------------------------------------------------------------------
/templates/portal_account/index.html:
--------------------------------------------------------------------------------
1 | {% extends "portal/base.html" %}
2 | {% load allauth i18n %}
3 | {% load django_bootstrap5 %}
4 | {% block body %}
5 | {% endblock body %}
6 | {% block content %}
7 |