diff --git a/.kiro/specs/black-to-ruff-migration/design.md b/.kiro/specs/black-to-ruff-migration/design.md
new file mode 100644
index 00000000..b4b2a9e5
--- /dev/null
+++ b/.kiro/specs/black-to-ruff-migration/design.md
@@ -0,0 +1,153 @@
+# Design Document
+
+## Overview
+
+This design outlines the migration from Black and isort to Ruff for the We All Code Django project. Ruff is a fast Python linter and formatter written in Rust that can replace both Black (formatting) and isort (import sorting) with a single tool. The migration will maintain existing code style preferences while consolidating tooling and improving performance.
+
+## Architecture
+
+### Tool Replacement Strategy
+
+The migration follows a direct replacement approach:
+
+- **Black** → **Ruff formatter** (maintains Black-compatible formatting)
+- **isort** → **Ruff import sorting** (maintains isort-compatible import organization)
+
+### Configuration Approach
+
+Ruff will be configured in `pyproject.toml` using the `[tool.ruff]` section with subsections for:
+
+- General settings (line length, target Python version, exclusions)
+- Formatter settings (`[tool.ruff.format]`)
+- Import sorting settings (`[tool.ruff.isort]`)
+- Linting rules (`[tool.ruff.lint]`)
+
+## Components and Interfaces
+
+### Configuration Files
+
+#### pyproject.toml Updates
+
+**Rationale**: Centralizing all tool configuration in pyproject.toml follows Python packaging standards and simplifies maintenance.
+
+- Remove `[tool.black]` section
+- Remove `[tool.isort]` section
+- Add comprehensive `[tool.ruff]` configuration
+- Add Ruff as a development dependency in the "# Development & Debugging" section alongside django-debug-toolbar
+- Remove Black and isort from dependencies if present
+
+#### Pre-commit Configuration Updates
+
+**Rationale**: Maintaining pre-commit integration ensures code quality checks remain automated and consistent across the development team.
+
+- Replace Black hook (currently using psf/black rev 23.3.0) with Ruff formatter hook
+- Replace isort hook (currently using pycqa/isort rev 5.12.0) with Ruff import sorting hook
+- Maintain existing exclusion patterns for migrations and .vscode folders
+- Keep all other pre-commit hooks unchanged (trailing-whitespace, end-of-file-fixer, etc.)
+
+#### Documentation Updates
+
+**Rationale**: Comprehensive documentation updates ensure all team members and new contributors understand the current tooling and maintain consistency across the project.
+
+- Update `.kiro/steering/tech.md` Code Quality Tools section to reference Ruff instead of Black and isort
+- Update `.kiro/steering/structure.md` Development Conventions section to reference Ruff formatting
+- Check and update `README.md` if it contains references to Black or isort
+- Update any developer setup instructions to include Ruff-specific commands
+- Ensure all documentation maintains consistency with Ruff usage
+
+### Ruff Configuration Sections
+
+Based on the current Black and isort configuration, Ruff will be configured as follows:
+
+#### Core Settings
+
+```toml
+[tool.ruff]
+target-version = "py311"
+exclude = [migrations, build artifacts, etc.]
+```
+
+#### Import Sorting Settings
+
+**Rationale**: Django-aware import sorting maintains the existing project's import organization patterns while leveraging Ruff's performance benefits. This ensures proper separation of Django imports from other third-party libraries, maintaining the project's existing import organization standards.
+
+
+
+## Data Models
+
+No data models are affected by this migration as it only changes development tooling configuration.
+
+## Error Handling
+
+### Migration Validation
+
+- Verify Ruff produces equivalent formatting to Black on existing codebase
+- Ensure import sorting maintains Django-aware section organization
+- Test pre-commit hooks function correctly with new configuration
+
+### Rollback Strategy
+
+- Keep backup of original Black/isort configuration
+- Document steps to revert if issues are discovered
+- Maintain git history for easy rollback
+
+## Testing Strategy
+
+### Configuration Testing
+
+1. **Format Consistency Test**: Run Ruff formatter on existing codebase and verify minimal changes
+2. **Import Sorting Test**: Verify Ruff import sorting maintains Django section organization
+3. **Pre-commit Integration Test**: Test pre-commit hooks with Ruff configuration
+4. **Exclusion Pattern Test**: Verify migrations and other excluded files are not processed
+
+### Validation Steps
+
+**Rationale**: These validation steps ensure the migration maintains code quality and formatting consistency while verifying all requirements are met.
+
+1. Install Ruff and configure in pyproject.toml
+2. Run `docker compose run --rm app uv run ruff format --check .` on codebase to verify compatibility (respects pyproject.toml settings)
+3. Run `docker compose run --rm app uv run ruff check --select I .` to test import sorting (respects pyproject.toml settings)
+4. Test pre-commit hooks in containerized environment using `docker compose run --rm app pre-commit run --all-files`
+5. Compare output with existing Black/isort formatting to ensure consistency
+6. Verify uv commands work correctly with Ruff (addresses Requirement 4.4)
+7. Confirm migrations are properly excluded from formatting and linting
+
+### Performance Verification
+
+- Measure formatting speed improvement with Ruff vs Black+isort
+- Verify pre-commit hook execution time improvement
+
+## Implementation Considerations
+
+### Dependency Management
+
+**Rationale**: Proper dependency management ensures Ruff is available in all development environments and follows the project's existing organizational patterns.
+
+- Ruff will be added to the "# Development & Debugging" section in pyproject.toml dependencies (addresses Requirement 4.1, 4.3)
+- Black and isort configurations will be removed from pyproject.toml (addresses Requirement 4.2)
+- uv will handle Ruff installation and version management (addresses Requirement 4.4)
+- Ruff will be placed appropriately within the development tools section to maintain logical grouping
+
+### Backward Compatibility
+
+- Ruff's Black-compatible formatter ensures existing code style is maintained
+- Django-aware import sorting preserves current import organization
+- Line length and exclusion patterns remain unchanged
+
+### Team Adoption
+
+- Developers will need to update their local pre-commit hooks
+- IDE integrations may need to be updated to use Ruff instead of Black
+- Documentation will guide developers through the transition
+
+## Requirements Traceability
+
+This design addresses all requirements from the requirements document:
+
+**Requirement 1 (Tool Replacement)**: Addressed through pyproject.toml configuration sections that replace Black and isort with Ruff while maintaining migration exclusions, and Django-aware import sorting.
+
+**Requirement 2 (Pre-commit Integration)**: Addressed through pre-commit configuration updates that replace Black and isort hooks with Ruff equivalents while maintaining existing exclusion patterns.
+
+**Requirement 3 (Documentation Updates)**: Addressed through systematic updates to steering documents, README, and developer setup instructions to reflect Ruff usage consistently.
+
+**Requirement 4 (Dependency Management)**: Addressed through adding Ruff to development dependencies and removing Black/isort configurations, with uv handling installation and version management.
diff --git a/.kiro/specs/black-to-ruff-migration/requirements.md b/.kiro/specs/black-to-ruff-migration/requirements.md
new file mode 100644
index 00000000..4602d76e
--- /dev/null
+++ b/.kiro/specs/black-to-ruff-migration/requirements.md
@@ -0,0 +1,52 @@
+# Requirements Document
+
+## Introduction
+
+This feature involves migrating the We All Code Django project from using Black (code formatter) and isort (import sorter) to Ruff, which is a faster, all-in-one Python linter and formatter that can replace both tools. Ruff provides the same formatting capabilities as Black while also offering linting and import sorting functionality in a single, faster tool.
+
+## Requirements
+
+### Requirement 1: Tool Replacement
+
+**User Story:** As a developer, I want to use Ruff instead of Black and isort, so that I have faster code formatting and linting with a single tool.
+
+#### Acceptance Criteria
+
+1. WHEN the project is configured THEN Ruff SHALL replace Black as the code formatter
+2. WHEN the project is configured THEN Ruff SHALL replace isort for import sorting
+3. WHEN Ruff is configured THEN it SHALL exclude migrations from formatting (same as current Black config)
+4. WHEN Ruff is configured THEN it SHALL maintain Django-aware import sorting sections with proper separation of Django imports from other third-party libraries
+
+### Requirement 2: Pre-commit Integration
+
+**User Story:** As a developer, I want the pre-commit hooks updated to use Ruff, so that code quality checks run automatically before commits.
+
+#### Acceptance Criteria
+
+1. WHEN pre-commit hooks are updated THEN they SHALL use Ruff instead of Black and isort
+2. WHEN pre-commit runs THEN it SHALL format code using Ruff
+3. WHEN pre-commit runs THEN it SHALL sort imports using Ruff
+4. WHEN pre-commit runs THEN it SHALL maintain the same exclusion patterns as before
+
+### Requirement 3: Documentation Updates
+
+**User Story:** As a developer, I want all project documentation updated to reflect the Ruff migration, so that new contributors understand the current tooling and existing developers have accurate reference materials.
+
+#### Acceptance Criteria
+
+1. WHEN documentation is updated THEN .kiro/steering/tech.md SHALL reference Ruff instead of Black and isort in the Code Quality Tools section
+2. WHEN documentation is updated THEN .kiro/steering/structure.md SHALL reference Ruff formatting conventions instead of Black
+3. WHEN documentation is updated THEN README.md SHALL be updated if it contains references to Black or isort
+4. WHEN documentation is updated THEN any developer setup instructions SHALL include Ruff-specific commands
+5. WHEN documentation is updated THEN all references to code formatting tools SHALL be consistent with Ruff usage
+
+### Requirement 4: Dependency Management
+
+**User Story:** As a developer, I want Ruff to be added as a project dependency, so that it's available in the development environment.
+
+#### Acceptance Criteria
+
+1. WHEN dependencies are updated THEN Ruff SHALL be added to pyproject.toml
+2. WHEN dependencies are updated THEN Black and isort SHALL be removed from dependencies (if present)
+3. WHEN Ruff is added THEN it SHALL be in the appropriate dependency group for development tools
+4. WHEN the configuration is complete THEN Ruff SHALL be usable via uv commands
diff --git a/.kiro/specs/black-to-ruff-migration/tasks.md b/.kiro/specs/black-to-ruff-migration/tasks.md
new file mode 100644
index 00000000..fdc00915
--- /dev/null
+++ b/.kiro/specs/black-to-ruff-migration/tasks.md
@@ -0,0 +1,66 @@
+# Implementation Plan
+
+- [x] 1. Configure Ruff in pyproject.toml
+
+ - Remove existing [tool.black] and [tool.isort] configuration sections
+ - Add comprehensive [tool.ruff] configuration with general settings (target-version = "py311", exclude migrations)
+ - Add Ruff as a development dependency in the "# Development & Debugging" section alongside django-debug-toolbar
+ - Add [tool.ruff.lint] section with comprehensive linting rules
+ - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 4.1, 4.2, 4.3, 4.4_
+
+- [x] 2. Update pre-commit configuration
+
+ - Replace Black hook (psf/black) with Ruff formatter hook (charliermarsh/ruff-pre-commit)
+ - Replace isort hook (pycqa/isort) with Ruff import sorting hook using ruff-check
+ - Maintain existing exclusion patterns for migrations and .vscode folders
+ - Keep all other pre-commit hooks unchanged (trailing-whitespace, end-of-file-fixer, etc.)
+ - _Requirements: 2.1, 2.2, 2.3, 2.4_
+
+- [x] 3. Update documentation files
+- [x] 3.1 Update .kiro/steering/tech.md
+
+ - Replace Black and isort references with Ruff in Code Quality Tools section
+ - Update tool descriptions to reflect Ruff's combined formatting and linting capabilities
+ - _Requirements: 3.1, 3.6_
+
+- [x] 3.2 Update .kiro/steering/structure.md
+
+ - Replace Black formatter references with Ruff in Development Conventions section
+ - Update code style documentation to reference Ruff instead of Black and isort
+ - Maintain Django-aware import sorting documentation
+ - _Requirements: 3.2, 3.6_
+
+- [x] 3.3 Check and update README.md if needed
+
+ - Search for any references to Black or isort in README.md (none found)
+ - Update developer setup instructions to include Ruff-specific commands if present (none needed)
+ - Ensure consistency with Ruff usage throughout documentation
+ - _Requirements: 3.3, 3.4, 3.5_
+
+- [-] 4. Enhance Ruff configuration for complete migration
+- [ ] 4.1 Complete pyproject.toml Ruff configuration
+
+ - Add exclude patterns for migrations and other directories
+ - Add [tool.ruff.isort] section with Django-aware import sorting (known-django, section-order, combine-as-imports)
+ - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5_
+
+- [x] 5. Test Ruff configuration
+- [x] 5.1 Validate formatting compatibility
+
+ - Run `docker compose run --rm app uv run ruff format --check .` on existing codebase to verify minimal changes
+ - Compare Ruff output with current formatting to ensure consistency
+ - Verify migrations are excluded from formatting (configured in pyproject.toml)
+ - _Requirements: 1.1, 1.3, 1.4_
+
+- [x] 5.2 Validate import sorting
+
+ - Run `docker compose run --rm app uv run ruff check --select I .` to test import sorting functionality
+ - Verify Django-aware section organization is maintained
+ - Test that import sorting follows isort-compatible behavior using `docker compose run --rm app uv run ruff check --select I --fix .`
+ - _Requirements: 1.2, 1.5_
+
+- [x] 5.3 Test pre-commit integration
+ - Run `docker compose run --rm app pre-commit run --all-files` to test pre-commit hooks in containerized environment
+ - Test that Ruff hooks execute correctly and maintain exclusion patterns
+ - Verify pre-commit performance improvement with Ruff using `docker compose run --rm app pre-commit run ruff-format ruff-check`
+ - _Requirements: 2.1, 2.2, 2.3, 2.4_
diff --git a/.kiro/steering/structure.md b/.kiro/steering/structure.md
index 15af98f1..c6f77284 100644
--- a/.kiro/steering/structure.md
+++ b/.kiro/steering/structure.md
@@ -91,8 +91,8 @@ Extends django-allauth for custom authentication:
### Code Style
-- Black formatter with 79 character line length
-- isort for import organization with Django-aware sections
+- Ruff formatter and linter with 79 character line length
+- Ruff import sorting with Django-aware sections
- Migrations excluded from formatting
### Database
diff --git a/.kiro/steering/tech.md b/.kiro/steering/tech.md
index 1629cb1d..3ec41e09 100644
--- a/.kiro/steering/tech.md
+++ b/.kiro/steering/tech.md
@@ -25,8 +25,7 @@
## Code Quality Tools
-- **Black** - Code formatter (line length: 79)
-- **isort** - Import sorting with Black profile
+- **Ruff** - Fast Python linter and formatter with import sorting
- **django-nose** - Test runner
## Common Commands
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3b866a49..9dae4a87 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,36 +1,39 @@
+exclude: "^docs/|/migrations/|devcontainer.json"
+default_stages: [pre-commit]
+minimum_pre_commit_version: "3.2.0"
+
default_language_version:
python: python3.11
-# Ignore all 'migration' folders and .vscode folder
-exclude: '^(\.vscode\/?)|(.*\/migrations\/.*)$'
-
repos:
# pre-commit hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v3.2.0
+ rev: v5.0.0
hooks:
- - id: no-commit-to-branch
- args: [--branch, main]
- id: trailing-whitespace
- id: end-of-file-fixer
- - id: check-yaml
- id: check-json
- id: check-toml
+ - id: check-xml
+ - id: check-yaml
+ - id: debug-statements
+ - id: check-builtin-literals
+ - id: check-case-conflict
+ - id: check-docstring-first
+ - id: detect-private-key
+ - id: no-commit-to-branch
+ args: [--branch, main]
- id: check-added-large-files
- id: check-merge-conflict
- - id: detect-private-key
- id: mixed-line-ending
args: [--fix=lf]
- # isort
- - repo: https://github.com/pycqa/isort
- rev: 5.12.0
- hooks:
- - id: isort
-
- # black
- - repo: https://github.com/psf/black
- rev: 23.3.0
+ # Run the Ruff linter.
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.12.5
hooks:
- - id: black
- args: [--preview]
+ # Linter
+ - id: ruff-check
+ args: [--fix, --exit-non-zero-on-fix]
+ # Formatter
+ - id: ruff-format
diff --git a/accounts/urls.py b/accounts/urls.py
index c72ddc2c..a85f8e99 100644
--- a/accounts/urls.py
+++ b/accounts/urls.py
@@ -1,11 +1,9 @@
from django.conf.urls import include
from django.urls import path
-from .views import (
- AccountHomeView,
- LoginView,
- SignupView,
-)
+from .views import AccountHomeView
+from .views import LoginView
+from .views import SignupView
urlpatterns = [
path("", AccountHomeView.as_view(), name="account_home"),
diff --git a/accounts/views.py b/accounts/views.py
index eb7787c1..159ea1e7 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -1,34 +1,25 @@
-from django.conf import settings
+from allauth.account.views import LoginView as AllAuthLoginView
+from allauth.account.views import SignupView as AllAuthSignupView
from django.contrib import messages
from django.contrib.auth.decorators import login_required
-from django.shortcuts import (
- get_object_or_404,
- redirect,
- render,
-)
+from django.shortcuts import get_object_or_404
+from django.shortcuts import redirect
+from django.shortcuts import render
+from django.urls import reverse
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
-
-from allauth.account.views import (
- LoginView as AllAuthLoginView,
- SignupView as AllAuthSignupView,
-)
from meta.views import MetadataMixin
-from coderdojochi.forms import (
- CDCModelForm,
- GuardianForm,
- MentorForm,
-)
-from coderdojochi.models import (
- Guardian,
- MeetingOrder,
- Mentor,
- MentorOrder,
- Order,
- Student,
-)
+from coderdojochi.forms import CDCModelForm
+from coderdojochi.forms import GuardianForm
+from coderdojochi.forms import MentorForm
+from coderdojochi.models import Guardian
+from coderdojochi.models import MeetingOrder
+from coderdojochi.models import Mentor
+from coderdojochi.models import MentorOrder
+from coderdojochi.models import Order
+from coderdojochi.models import Student
class SignupView(MetadataMixin, AllAuthSignupView):
@@ -65,13 +56,12 @@ def dispatch(self, *args, **kwargs):
if not self.request.user.role:
if "next" in self.request.GET:
return redirect(
- f"{reverse('welcome')}?next={self.request.GET['next']}"
- )
- else:
- messages.warning(
- self.request,
- "Tell us a little about yourself before going on account.",
+ f"{reverse('welcome')}?next={self.request.GET['next']}",
)
+ messages.warning(
+ self.request,
+ "Tell us a little about yourself before going on account.",
+ )
return redirect("welcome")
return super().dispatch(*args, **kwargs)
@@ -103,15 +93,17 @@ def get_context_data_for_mentor(self):
)
upcoming_sessions = orders.filter(
- is_active=True, session__start_date__gte=timezone.now()
+ is_active=True,
+ session__start_date__gte=timezone.now(),
).order_by("session__start_date")
past_sessions = orders.filter(
- is_active=True, session__start_date__lte=timezone.now()
+ is_active=True,
+ session__start_date__lte=timezone.now(),
).order_by("session__start_date")
meeting_orders = MeetingOrder.objects.select_related().filter(
- mentor=mentor
+ mentor=mentor,
)
upcoming_meetings = meeting_orders.filter(
@@ -207,11 +199,15 @@ def post_for_mentor(self, **kwargs):
mentor = context["mentor"]
form = MentorForm(
- self.request.POST, self.request.FILES, instance=mentor
+ self.request.POST,
+ self.request.FILES,
+ instance=mentor,
)
user_form = CDCModelForm(
- self.request.POST, self.request.FILES, instance=mentor.user
+ self.request.POST,
+ self.request.FILES,
+ instance=mentor.user,
)
if form.is_valid() and user_form.is_valid():
@@ -221,10 +217,10 @@ def post_for_mentor(self, **kwargs):
return redirect("account_home")
- else:
- messages.error(
- self.request, "There was an error. Please try again."
- )
+ messages.error(
+ self.request,
+ "There was an error. Please try again.",
+ )
context["form"] = form
context["user_form"] = user_form
@@ -247,10 +243,10 @@ def post_for_guardian(self, **kwargs):
return redirect("account_home")
- else:
- messages.error(
- self.request, "There was an error. Please try again."
- )
+ messages.error(
+ self.request,
+ "There was an error. Please try again.",
+ )
context["form"] = form
context["user_form"] = user_form
diff --git a/coderdojochi/admin.py b/coderdojochi/admin.py
index ceb86344..d5052cbf 100644
--- a/coderdojochi/admin.py
+++ b/coderdojochi/admin.py
@@ -3,39 +3,32 @@
from django.contrib import admin
from django.contrib.auth import get_user_model
-from django.db.models import (
- Case,
- Count,
- When,
-)
+from django.db.models import Case
+from django.db.models import Count
+from django.db.models import When
from django.urls import reverse
from django.utils import timezone
from django.utils.html import format_html
-
from import_export import resources
-from import_export.admin import (
- ImportExportActionModelAdmin,
- ImportExportMixin,
-)
+from import_export.admin import ImportExportActionModelAdmin
+from import_export.admin import ImportExportMixin
from import_export.fields import Field
-from .models import (
- Course,
- Donation,
- Equipment,
- EquipmentType,
- Guardian,
- Location,
- Meeting,
- MeetingOrder,
- MeetingType,
- Mentor,
- MentorOrder,
- Order,
- RaceEthnicity,
- Session,
- Student,
-)
+from .models import Course
+from .models import Donation
+from .models import Equipment
+from .models import EquipmentType
+from .models import Guardian
+from .models import Location
+from .models import Meeting
+from .models import MeetingOrder
+from .models import MeetingType
+from .models import Mentor
+from .models import MentorOrder
+from .models import Order
+from .models import RaceEthnicity
+from .models import Session
+from .models import Student
User = get_user_model()
@@ -95,7 +88,7 @@ def role_link(self, obj):
query=obj.email,
role=obj.role,
)
- elif obj.role == "guardian":
+ if obj.role == "guardian":
return format_html(
'{role}',
url=reverse("admin:coderdojochi_guardian_changelist"),
@@ -174,9 +167,9 @@ def get_queryset(self, request):
When(
mentororder__is_active=True,
then=1,
- )
- )
- )
+ ),
+ ),
+ ),
)
return qs
@@ -195,7 +188,8 @@ def user_link(self, obj):
return format_html(
'{user}',
url=reverse(
- "admin:coderdojochi_cdcuser_change", args=(obj.user.id,)
+ "admin:coderdojochi_cdcuser_change",
+ args=(obj.user.id,),
),
user=obj.user,
)
@@ -305,7 +299,7 @@ def get_queryset(self, request):
qs = super(GuardianAdmin, self).get_queryset(request)
qs = qs.select_related()
qs = qs.annotate(student_count=Count("student")).order_by(
- "-student_count"
+ "-student_count",
)
return qs
@@ -313,7 +307,8 @@ def user_link(self, obj):
return format_html(
'{name}',
url=reverse(
- "admin:coderdojochi_cdcuser_change", args=(obj.user.id,)
+ "admin:coderdojochi_cdcuser_change",
+ args=(obj.user.id,),
),
name=obj.user,
)
@@ -387,7 +382,7 @@ def import_obj(self, obj, data, dry_run):
obj.guardian = Guardian.objects.get(user__email=guardian_email)
except Guardian.DoesNotExist:
raise ImportError(
- f"guardian with email {guardian_email} not found"
+ f"guardian with email {guardian_email} not found",
)
if not dry_run:
@@ -467,9 +462,9 @@ def get_queryset(self, request):
When(
order__is_active=True,
then=1,
- )
- )
- )
+ ),
+ ),
+ ),
)
return qs
@@ -477,7 +472,8 @@ def guardian_link(self, obj):
return format_html(
'{name}',
url=reverse(
- "admin:coderdojochi_guardian_change", args=(obj.guardian.id,)
+ "admin:coderdojochi_guardian_change",
+ args=(obj.guardian.id,),
),
name=obj.guardian.full_name,
)
@@ -624,7 +620,7 @@ class SessionAdmin(ImportExportMixin, ImportExportActionModelAdmin):
"cost",
"minimum_cost",
"maximum_cost",
- )
+ ),
},
),
(
@@ -673,7 +669,8 @@ def mentor_count_link(self, obj):
url=reverse("admin:coderdojochi_mentororder_changelist"),
query=obj.id,
count=MentorOrder.objects.filter(
- session__id=obj.id, is_active=True
+ session__id=obj.id,
+ is_active=True,
).count(),
)
@@ -762,7 +759,8 @@ def get_student_link(self, obj):
return format_html(
'{student}',
url=reverse(
- "admin:coderdojochi_student_change", args=(obj.student.id,)
+ "admin:coderdojochi_student_change",
+ args=(obj.student.id,),
),
student=obj.student,
)
@@ -773,7 +771,8 @@ def get_guardian_link(self, obj):
return format_html(
'{guardian}',
url=reverse(
- "admin:coderdojochi_guardian_change", args=(obj.guardian.id,)
+ "admin:coderdojochi_guardian_change",
+ args=(obj.guardian.id,),
),
guardian=obj.guardian,
)
diff --git a/coderdojochi/cron.py b/coderdojochi/cron.py
index dfcea363..a16a6672 100644
--- a/coderdojochi/cron.py
+++ b/coderdojochi/cron.py
@@ -1,19 +1,14 @@
from datetime import timedelta
+import arrow
from django.conf import settings
from django.utils import timezone
+from django_cron import CronJobBase
+from django_cron import Schedule
-import arrow
-from django_cron import (
- CronJobBase,
- Schedule,
-)
-
-from coderdojochi.models import (
- MentorOrder,
- Order,
- Session,
-)
+from coderdojochi.models import MentorOrder
+from coderdojochi.models import Order
+from coderdojochi.models import Session
from coderdojochi.util import email
@@ -69,9 +64,7 @@ def do(self):
.format("dddd, MMMM D, YYYY")
),
"class_start_time": (
- arrow.get(order.session.start_date)
- .to("local")
- .format("h:mma")
+ arrow.get(order.session.start_date).to("local").format("h:mma")
),
"class_end_date": (
arrow.get(order.session.end_date)
@@ -79,9 +72,7 @@ def do(self):
.format("dddd, MMMM D, YYYY")
),
"class_end_time": (
- arrow.get(order.session.end_date)
- .to("local")
- .format("h:mma")
+ arrow.get(order.session.end_date).to("local").format("h:mma")
),
"class_location_name": order.session.location.name,
"class_location_address": order.session.location.address,
@@ -89,9 +80,7 @@ def do(self):
"class_location_state": order.session.location.state,
"class_location_zip": order.session.location.zip,
"class_additional_info": order.session.additional_info,
- "class_url": (
- f"{settings.SITE_URL}{order.session.get_absolute_url()}"
- ),
+ "class_url": (f"{settings.SITE_URL}{order.session.get_absolute_url()}"),
"class_calendar_url": (
f"{settings.SITE_URL}{order.session.get_calendar_url()}"
),
@@ -103,9 +92,7 @@ def do(self):
),
"order_id": order.id,
"online_video_link": order.session.online_video_link,
- "online_video_description": (
- order.session.online_video_description
- ),
+ "online_video_description": (order.session.online_video_description),
}
email(
@@ -141,9 +128,7 @@ def do(self):
.format("dddd, MMMM D, YYYY")
),
"class_start_time": (
- arrow.get(order.session.start_date)
- .to("local")
- .format("h:mma")
+ arrow.get(order.session.start_date).to("local").format("h:mma")
),
"class_end_date": (
arrow.get(order.session.end_date)
@@ -151,9 +136,7 @@ def do(self):
.format("dddd, MMMM D, YYYY")
),
"class_end_time": (
- arrow.get(order.session.end_date)
- .to("local")
- .format("h:mma")
+ arrow.get(order.session.end_date).to("local").format("h:mma")
),
"class_location_name": order.session.location.name,
"class_location_address": order.session.location.address,
@@ -161,9 +144,7 @@ def do(self):
"class_location_state": order.session.location.state,
"class_location_zip": order.session.location.zip,
"class_additional_info": order.session.additional_info,
- "class_url": (
- f"{settings.SITE_URL}{order.session.get_absolute_url()}"
- ),
+ "class_url": (f"{settings.SITE_URL}{order.session.get_absolute_url()}"),
"class_calendar_url": (
f"{settings.SITE_URL}{order.session.get_calendar_url()}"
),
@@ -175,9 +156,7 @@ def do(self):
),
"order_id": order.id,
"online_video_link": order.session.online_video_link,
- "online_video_description": (
- order.session.online_video_description
- ),
+ "online_video_description": (order.session.online_video_description),
}
email(
@@ -237,14 +216,10 @@ def do(self):
"class_url": f"{settings.SITE_URL}{order.session.get_absolute_url()}",
"class_calendar_url": f"{settings.SITE_URL}{order.session.get_calendar_url()}",
"microdata_start_date": (
- arrow.get(order.session.start_date)
- .to("local")
- .isoformat()
+ arrow.get(order.session.start_date).to("local").isoformat()
),
"microdata_end_date": (
- arrow.get(order.session.end_date)
- .to("local")
- .isoformat()
+ arrow.get(order.session.end_date).to("local").isoformat()
),
"order_id": order.id,
"online_video_link": order.session.online_video_link,
@@ -305,14 +280,10 @@ def do(self):
"class_url": f"{settings.SITE_URL}{order.session.get_absolute_url()}",
"class_calendar_url": f"{settings.SITE_URL}{order.session.get_calendar_url()}",
"microdata_start_date": (
- arrow.get(order.session.start_date)
- .to("local")
- .isoformat()
+ arrow.get(order.session.start_date).to("local").isoformat()
),
"microdata_end_date": (
- arrow.get(order.session.end_date)
- .to("local")
- .isoformat()
+ arrow.get(order.session.end_date).to("local").isoformat()
),
"order_id": order.id,
"online_video_link": order.session.online_video_link,
diff --git a/coderdojochi/custom_storages.py b/coderdojochi/custom_storages.py
index ecbf465f..7d4962bd 100644
--- a/coderdojochi/custom_storages.py
+++ b/coderdojochi/custom_storages.py
@@ -1,5 +1,4 @@
from django.conf import settings
-
from storages.backends.s3boto import S3BotoStorage
diff --git a/coderdojochi/factories.py b/coderdojochi/factories.py
index c40049c3..a42aec41 100644
--- a/coderdojochi/factories.py
+++ b/coderdojochi/factories.py
@@ -3,14 +3,12 @@
import factory
from pytz import utc
-from .models import (
- CDCUser,
- Course,
- Location,
- Mentor,
- PartnerPasswordAccess,
- Session,
-)
+from .models import CDCUser
+from .models import Course
+from .models import Location
+from .models import Mentor
+from .models import PartnerPasswordAccess
+from .models import Session
class CourseFactory(factory.DjangoModelFactory):
diff --git a/coderdojochi/forms.py b/coderdojochi/forms.py
index 7db7c0c6..597481dd 100644
--- a/coderdojochi/forms.py
+++ b/coderdojochi/forms.py
@@ -1,37 +1,30 @@
import re
+import html5.forms.widgets as html5_widgets
+from dateutil.relativedelta import relativedelta
from django import forms
from django.contrib.auth import get_user_model
from django.core.files.images import get_image_dimensions
-from django.forms import (
- FileField,
- Form,
- ModelForm,
- ValidationError,
-)
+from django.forms import FileField
+from django.forms import Form
+from django.forms import ModelForm
+from django.forms import ValidationError
from django.urls import reverse_lazy
-from django.utils import (
- dateformat,
- timezone,
-)
+from django.utils import dateformat
+from django.utils import timezone
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.text import format_lazy
-
-import html5.forms.widgets as html5_widgets
from django_recaptcha.fields import ReCaptchaField
from django_recaptcha.widgets import ReCaptchaV3
-from dateutil.relativedelta import relativedelta
-from coderdojochi.models import (
- CDCUser,
- Donation,
- Guardian,
- Mentor,
- RaceEthnicity,
- Session,
- Student,
-)
+from coderdojochi.models import CDCUser
+from coderdojochi.models import Donation
+from coderdojochi.models import Guardian
+from coderdojochi.models import Mentor
+from coderdojochi.models import RaceEthnicity
+from coderdojochi.models import Session
+from coderdojochi.models import Student
class CDCForm(Form):
@@ -42,18 +35,19 @@ def _clean_fields(self):
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
value = field.widget.value_from_datadict(
- self.data, self.files, self.add_prefix(name)
+ self.data,
+ self.files,
+ self.add_prefix(name),
)
try:
if isinstance(field, FileField):
initial = self.initial.get(name, field.initial)
value = field.clean(value, initial)
+ elif isinstance(value, str):
+ value = field.clean(value.strip())
else:
- if isinstance(value, str):
- value = field.clean(value.strip())
- else:
- value = field.clean(value)
+ value = field.clean(value)
self.cleaned_data[name] = value
@@ -73,22 +67,23 @@ def _clean_fields(self):
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
value = field.widget.value_from_datadict(
- self.data, self.files, self.add_prefix(name)
+ self.data,
+ self.files,
+ self.add_prefix(name),
)
try:
if isinstance(field, FileField):
initial = self.initial.get(name, field.initial)
value = field.clean(value, initial)
+ elif isinstance(value, str):
+ # regex normalizes carriage return
+ # and cuts them to two at most
+ value = re.sub(r"\r\n", "\n", value)
+ value = re.sub(r"\n{3,}", "\n\n", value)
+ value = field.clean(value.strip())
else:
- if isinstance(value, str):
- # regex normalizes carriage return
- # and cuts them to two at most
- value = re.sub(r"\r\n", "\n", value)
- value = re.sub(r"\n{3,}", "\n\n", value)
- value = field.clean(value.strip())
- else:
- value = field.clean(value)
+ value = field.clean(value)
self.cleaned_data[name] = value
@@ -135,7 +130,7 @@ class MentorForm(CDCModelForm):
"placeholder": "Short Bio",
"class": "form-control",
"rows": 4,
- }
+ },
),
label="Short Bio",
required=False,
@@ -143,7 +138,7 @@ class MentorForm(CDCModelForm):
gender = forms.CharField(
widget=forms.TextInput(
- attrs={"placeholder": "", "class": "form-control"}
+ attrs={"placeholder": "", "class": "form-control"},
),
label="Gender",
required=True,
@@ -162,7 +157,7 @@ class MentorForm(CDCModelForm):
work_place = forms.CharField(
widget=forms.TextInput(
- attrs={"placeholder": "", "class": "form-control"}
+ attrs={"placeholder": "", "class": "form-control"},
),
label="Work Place",
required=False,
@@ -170,7 +165,7 @@ class MentorForm(CDCModelForm):
phone = forms.CharField(
widget=forms.TextInput(
- attrs={"placeholder": "", "class": "form-control"}
+ attrs={"placeholder": "", "class": "form-control"},
),
label="Phone",
required=False,
@@ -178,7 +173,7 @@ class MentorForm(CDCModelForm):
home_address = forms.CharField(
widget=forms.TextInput(
- attrs={"placeholder": "", "class": "form-control"}
+ attrs={"placeholder": "", "class": "form-control"},
),
label="Home Address",
required=False,
@@ -211,29 +206,27 @@ def clean_avatar(self):
if w > max_width or h > max_height:
raise forms.ValidationError(
f"Please use an image that is {max_width} x {max_height}px"
- " or smaller."
+ " or smaller.",
)
min_width = min_height = 500
if w < min_width or h < min_height:
raise forms.ValidationError(
f"Please use an image that is {min_width} x {min_height}px"
- " or larger."
+ " or larger.",
)
# validate content type
main, sub = avatar.content_type.split("/")
- if not (
- main == "image" and sub in ["jpeg", "pjpeg", "gif", "png"]
- ):
+ if not (main == "image" and sub in ["jpeg", "pjpeg", "gif", "png"]):
raise forms.ValidationError(
- "Please use a JPEG, GIF or PNG image."
+ "Please use a JPEG, GIF or PNG image.",
)
# validate file size
if len(avatar) > (2000 * 1024):
raise forms.ValidationError(
- "Avatar file size may not exceed 2MB."
+ "Avatar file size may not exceed 2MB.",
)
except AttributeError:
@@ -248,21 +241,21 @@ def clean_avatar(self):
class GuardianForm(CDCModelForm):
phone = forms.CharField(
widget=forms.TextInput(
- attrs={"placeholder": "Phone Number", "class": "form-control"}
+ attrs={"placeholder": "Phone Number", "class": "form-control"},
),
label="Phone Number",
)
zip = forms.CharField(
widget=forms.TextInput(
- attrs={"placeholder": "Zip Code", "class": "form-control"}
+ attrs={"placeholder": "Zip Code", "class": "form-control"},
),
label="Zip Code",
)
gender = forms.CharField(
widget=forms.TextInput(
- attrs={"placeholder": "", "class": "form-control"}
+ attrs={"placeholder": "", "class": "form-control"},
),
label="Gender",
required=True,
@@ -354,12 +347,14 @@ class StudentForm(CDCModelForm):
attrs={
"class": "form-control",
"min": dateformat.format(
- timezone.now() - relativedelta(years=19), "Y-m-d"
+ timezone.now() - relativedelta(years=19),
+ "Y-m-d",
),
"max": dateformat.format(
- timezone.now() - relativedelta(years=5), "Y-m-d"
+ timezone.now() - relativedelta(years=5),
+ "Y-m-d",
),
- }
+ },
),
)
@@ -369,14 +364,13 @@ class StudentForm(CDCModelForm):
"placeholder": "List any medications currently being taken.",
"class": "form-control hidden",
"rows": 5,
- }
+ },
),
label=format_html(
"{0} {1}",
"Medications",
mark_safe(
- 'expand'
+ 'expand',
),
),
required=False,
@@ -394,8 +388,7 @@ class StudentForm(CDCModelForm):
"{0} {1}",
"Medical Conditions",
mark_safe(
- 'expand'
+ 'expand',
),
),
required=False,
@@ -448,7 +441,8 @@ class DonationForm(ModelForm):
required=True,
)
user = forms.ModelChoiceField(
- queryset=CDCUser.objects.all(), required=True
+ queryset=CDCUser.objects.all(),
+ required=True,
)
amount = forms.CharField(label="Amount (dollars)")
diff --git a/coderdojochi/mixins.py b/coderdojochi/mixins.py
index 9c9f2837..ee8a8648 100644
--- a/coderdojochi/mixins.py
+++ b/coderdojochi/mixins.py
@@ -3,7 +3,7 @@
from django.urls import reverse
-class RoleTemplateMixin(object):
+class RoleTemplateMixin:
def get_template_names(self):
if self.request.user.is_authenticated:
if self.request.user.role == "mentor":
@@ -16,7 +16,7 @@ def get_template_names(self):
return [template_name]
-class RoleRedirectMixin(object):
+class RoleRedirectMixin:
def dispatch(self, request, *args, **kwargs):
session_obj = kwargs.get("session_obj")
user = request.user
@@ -27,9 +27,7 @@ def dispatch(self, request, *args, **kwargs):
"Please select one of the following options to continue.",
)
- next_url = (
- f"{reverse('welcome')}?next={session_obj.get_absolute_url()}"
- )
+ next_url = f"{reverse('welcome')}?next={session_obj.get_absolute_url()}"
if "enroll" in request.GET:
next_url += "&enroll=True"
@@ -37,5 +35,7 @@ def dispatch(self, request, *args, **kwargs):
return redirect(next_url)
return super(RoleRedirectMixin, self).dispatch(
- request, *args, **kwargs
+ request,
+ *args,
+ **kwargs,
)
diff --git a/coderdojochi/models/course.py b/coderdojochi/models/course.py
index 366709f0..db075078 100644
--- a/coderdojochi/models/course.py
+++ b/coderdojochi/models/course.py
@@ -1,9 +1,7 @@
from datetime import timedelta
-from django.core.validators import (
- MaxValueValidator,
- MinValueValidator,
-)
+from django.core.validators import MaxValueValidator
+from django.core.validators import MinValueValidator
from django.db import models
from .common import CommonInfo
diff --git a/coderdojochi/models/donation.py b/coderdojochi/models/donation.py
index 6478c630..e971d595 100644
--- a/coderdojochi/models/donation.py
+++ b/coderdojochi/models/donation.py
@@ -61,23 +61,20 @@ def get_admin_url(self):
def get_first_name(self):
if self.user:
return self.user.first_name
- else:
- return self.first_name
+ return self.first_name
get_first_name.short_description = "First Name"
def get_last_name(self):
if self.user:
return self.user.last_name
- else:
- return self.last_name
+ return self.last_name
get_last_name.short_description = "Last Name"
def get_email(self):
if self.user:
return self.user.email
- else:
- return self.email
+ return self.email
get_email.short_description = "Email"
diff --git a/coderdojochi/models/meeting.py b/coderdojochi/models/meeting.py
index 011eb350..8ca6efbf 100644
--- a/coderdojochi/models/meeting.py
+++ b/coderdojochi/models/meeting.py
@@ -133,7 +133,7 @@ def get_current_mentors(self):
meeting=self,
).values(
"mentor__id",
- )
+ ),
)
def get_mentor_count(self):
diff --git a/coderdojochi/models/mentor.py b/coderdojochi/models/mentor.py
index 334733cf..6afde80b 100644
--- a/coderdojochi/models/mentor.py
+++ b/coderdojochi/models/mentor.py
@@ -2,14 +2,10 @@
from django.db import models
from django.urls import reverse
-
from stdimage.models import StdImageField
-from ..notifications import (
- NewMentorBgCheckNotification,
- NewMentorNotification,
- NewMentorOrderNotification,
-)
+from ..notifications import NewMentorBgCheckNotification
+from ..notifications import NewMentorNotification
from .common import CommonInfo
from .race_ethnicity import RaceEthnicity
from .user import CDCUser
@@ -143,7 +139,7 @@ def get_absolute_url(self):
)
def get_avatar(self):
- if self.avatar and self.avatar_approved == True:
+ if self.avatar and self.avatar_approved:
return {
"url": f"{self.avatar.url}",
"thumbnail": {
@@ -164,14 +160,14 @@ def get_avatar(self):
"d": "mp",
"r": "pg",
"s": str(320),
- }
+ },
)
full_params = urlencode(
{
"d": "mp",
"r": "pg",
"s": str(500),
- }
+ },
)
slug_url = f"https://www.gravatar.com/avatar/{email_encoded}"
diff --git a/coderdojochi/models/mentor_order.py b/coderdojochi/models/mentor_order.py
index 92e60202..fb2a0501 100644
--- a/coderdojochi/models/mentor_order.py
+++ b/coderdojochi/models/mentor_order.py
@@ -1,5 +1,3 @@
-import os
-
from django.db import models
from ..notifications import NewMentorOrderNotification
@@ -57,7 +55,7 @@ def is_checked_in(self):
def save(self, *args, **kwargs):
num_orders = MentorOrder.objects.filter(
- mentor__id=self.mentor.id
+ mentor__id=self.mentor.id,
).count()
if self.pk is None and num_orders == 0:
diff --git a/coderdojochi/models/session.py b/coderdojochi/models/session.py
index d9655190..bf113fda 100644
--- a/coderdojochi/models/session.py
+++ b/coderdojochi/models/session.py
@@ -1,13 +1,10 @@
from datetime import timedelta
-from django.core.validators import (
- MaxValueValidator,
- MinValueValidator,
-)
+from django.core.validators import MaxValueValidator
+from django.core.validators import MinValueValidator
from django.db import models
from django.urls.base import reverse
from django.utils import formats
-from django.utils.functional import cached_property
from .common import CommonInfo
@@ -99,7 +96,9 @@ class Session(CommonInfo):
# Extra
additional_info = models.TextField(
- blank=True, null=True, help_text="Basic HTML allowed"
+ blank=True,
+ null=True,
+ help_text="Basic HTML allowed",
)
waitlist_mentors = models.ManyToManyField(
Mentor,
@@ -200,8 +199,7 @@ class Session(CommonInfo):
online_video_description = models.TextField(
"Online Video Description",
help_text=(
- "Information on how to connect to the video call. Basic HTML"
- " allowed."
+ "Information on how to connect to the video call. Basic HTML allowed."
),
blank=True,
null=True,
@@ -274,11 +272,12 @@ def save(self, *args, **kwargs):
if self.mentor_capacity is None:
self.mentor_capacity = int(self.capacity / 2)
- if self.mentor_capacity < 0:
+ # Ensure mentor_capacity is not negative
+ if self.mentor_capacity is None or self.mentor_capacity < 0:
self.mentor_capacity = 0
- # Capacity check
- if self.capacity < 0:
+ # Ensure capacity is not negative
+ if self.capacity is None or self.capacity < 0:
self.capacity = 0
super(Session, self).save(*args, **kwargs)
@@ -311,7 +310,9 @@ def get_checked_in_mentor_orders(self):
from .mentor_order import MentorOrder
return MentorOrder.objects.filter(
- session=self, is_active=True, check_in__isnull=False
+ session=self,
+ is_active=True,
+ check_in__isnull=False,
).order_by("mentor__user__last_name")
def get_current_orders(self, checked_in=None):
@@ -326,11 +327,14 @@ def get_current_orders(self, checked_in=None):
)
else:
orders = Order.objects.filter(
- is_active=True, session=self, check_in=None
+ is_active=True,
+ session=self,
+ check_in=None,
).order_by("student__last_name")
else:
orders = Order.objects.filter(
- is_active=True, session=self
+ is_active=True,
+ session=self,
).order_by("check_in", "student__last_name")
return orders
@@ -339,9 +343,7 @@ def get_active_student_count(self):
from .order import Order
return (
- Order.objects.filter(is_active=True, session=self)
- .values("student")
- .count()
+ Order.objects.filter(is_active=True, session=self).values("student").count()
)
def get_checked_in_students(self):
diff --git a/coderdojochi/models/student.py b/coderdojochi/models/student.py
index ee5451c9..22976e35 100644
--- a/coderdojochi/models/student.py
+++ b/coderdojochi/models/student.py
@@ -88,10 +88,7 @@ def get_age(self, date=timezone.now()):
return (
date.year
- self.birthday.year
- - (
- (date.month, date.day)
- < (self.birthday.month, self.birthday.day)
- )
+ - ((date.month, date.day) < (self.birthday.month, self.birthday.day))
)
get_age.short_description = "Age"
@@ -102,10 +99,9 @@ def get_clean_gender(self):
if self.gender.lower() in MALE:
return "male"
- elif self.gender.lower() in FEMALE:
+ if self.gender.lower() in FEMALE:
return "female"
- else:
- return "other"
+ return "other"
get_clean_gender.short_description = "Clean Gender"
@@ -114,29 +110,28 @@ def get_clean_gender_short(self):
if gender == "male":
return "m"
- elif gender == "female":
+ if gender == "female":
return "f"
- else:
- return "o"
+ return "o"
get_clean_gender_short.short_description = "Clean Gender Short"
# returns True if the student age is between minimum_age and maximum_age
def is_within_age_range(
- self, minimum_age, maximum_age, date=timezone.now()
+ self,
+ minimum_age,
+ maximum_age,
+ date=timezone.now(),
):
age = self.get_age(date)
if age >= minimum_age and age <= maximum_age:
return True
- else:
- return False
+ return False
def is_within_gender_limitation(self, limitation):
if limitation:
if self.get_clean_gender() in [limitation.lower(), "other"]:
return True
- else:
- return False
- else:
- return True
+ return False
+ return True
diff --git a/coderdojochi/notifications.py b/coderdojochi/notifications.py
index 3720d7a5..deb4e4c4 100644
--- a/coderdojochi/notifications.py
+++ b/coderdojochi/notifications.py
@@ -1,8 +1,7 @@
import logging
-from django.conf import settings
-
import requests
+from django.conf import settings
logger = logging.getLogger(__name__)
@@ -14,7 +13,7 @@ class SlackNotification:
}
def __init__(self):
- self.payload = DEFAULT_PAYLOAD
+ self.payload = self.DEFAULT_PAYLOAD.copy()
def send(self):
res = requests.post(
@@ -27,7 +26,7 @@ def send(self):
{
"msg": "Unable to send Slack notification",
"error": res.content,
- }
+ },
)
@@ -56,7 +55,7 @@ def __init__(self, mentor):
},
],
},
- ]
+ ],
}
@@ -89,7 +88,7 @@ def __init__(self, mentor_order):
{"type": "mrkdwn", "text": f"*Date*: \n{start_date}"},
],
},
- ]
+ ],
}
@@ -118,5 +117,5 @@ def __init__(self, mentor):
},
],
},
- ]
+ ],
}
diff --git a/coderdojochi/old_views.py b/coderdojochi/old_views.py
index 787b71c8..a4b5a340 100644
--- a/coderdojochi/old_views.py
+++ b/coderdojochi/old_views.py
@@ -4,47 +4,38 @@
from datetime import timedelta
from functools import reduce
+import arrow
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ObjectDoesNotExist
-from django.db.models import (
- Case,
- Count,
- IntegerField,
- When,
-)
+from django.db.models import Case
+from django.db.models import Count
+from django.db.models import IntegerField
+from django.db.models import When
from django.http import HttpResponse
-from django.shortcuts import (
- get_object_or_404,
- redirect,
- render,
-)
+from django.shortcuts import get_object_or_404
+from django.shortcuts import redirect
+from django.shortcuts import render
from django.urls import reverse
from django.utils import timezone
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt
-import arrow
-
-from coderdojochi.forms import (
- DonationForm,
- StudentForm,
-)
-from coderdojochi.models import (
- Donation,
- Equipment,
- EquipmentType,
- Guardian,
- Meeting,
- MeetingOrder,
- Mentor,
- MentorOrder,
- Order,
- Session,
- Student,
-)
+from coderdojochi.forms import DonationForm
+from coderdojochi.forms import StudentForm
+from coderdojochi.models import Donation
+from coderdojochi.models import Equipment
+from coderdojochi.models import EquipmentType
+from coderdojochi.models import Guardian
+from coderdojochi.models import Meeting
+from coderdojochi.models import MeetingOrder
+from coderdojochi.models import Mentor
+from coderdojochi.models import MentorOrder
+from coderdojochi.models import Order
+from coderdojochi.models import Session
+from coderdojochi.models import Student
from coderdojochi.util import email
logger = logging.getLogger(__name__)
@@ -65,7 +56,9 @@ def home(request, template_name="home.html"):
upcoming_classes = upcoming_classes[:3]
return render(
- request, template_name, {"upcoming_classes": upcoming_classes}
+ request,
+ template_name,
+ {"upcoming_classes": upcoming_classes},
)
@@ -83,7 +76,9 @@ def volunteer(request, template_name="volunteer.html"):
)
upcoming_meetings = Meeting.objects.filter(
- is_active=True, is_public=True, end_date__gte=timezone.now()
+ is_active=True,
+ is_public=True,
+ end_date__gte=timezone.now(),
).order_by("start_date")[:3]
return render(
@@ -99,11 +94,12 @@ def mentor_approve_avatar(request, pk=None):
if not request.user.is_staff:
messages.error(
- request, "You do not have permissions to moderate content."
+ request,
+ "You do not have permissions to moderate content.",
)
return redirect(
- f"{reverse('account_login')}?next={mentor.get_approve_avatar_url()}"
+ f"{reverse('account_login')}?next={mentor.get_approve_avatar_url()}",
)
mentor.avatar_approved = True
@@ -112,24 +108,20 @@ def mentor_approve_avatar(request, pk=None):
if mentor.background_check:
messages.success(
request,
- (
- f"{mentor.full_name}'s avatar approved and their account is"
- " now public."
- ),
+ (f"{mentor.full_name}'s avatar approved and their account is now public."),
)
return redirect(f"{reverse('mentors')}{mentor.id}")
- else:
- messages.success(
- request,
- (
- f"{mentor.full_name}'s avatar approved but they have yet to"
- " fill out the 'background search' form."
- ),
- )
+ messages.success(
+ request,
+ (
+ f"{mentor.full_name}'s avatar approved but they have yet to"
+ " fill out the 'background search' form."
+ ),
+ )
- return redirect("mentors")
+ return redirect("mentors")
@login_required
@@ -138,11 +130,12 @@ def mentor_reject_avatar(request, pk=None):
if not request.user.is_staff:
messages.error(
- request, "You do not have permissions to moderate content."
+ request,
+ "You do not have permissions to moderate content.",
)
return redirect(
- f"{reverse('account_login')}?next={mentor.get_reject_avatar_url()}"
+ f"{reverse('account_login')}?next={mentor.get_reject_avatar_url()}",
)
mentor.avatar_approved = False
@@ -170,7 +163,9 @@ def mentor_reject_avatar(request, pk=None):
@login_required
def student_detail(
- request, student_id=False, template_name="student_detail.html"
+ request,
+ student_id=False,
+ template_name="student_detail.html",
):
access = True
@@ -196,7 +191,8 @@ def student_detail(
if not access:
return redirect("account_home")
messages.error(
- request, "You do not have permissions to edit this student."
+ request,
+ "You do not have permissions to edit this student.",
)
if request.method == "POST":
@@ -204,7 +200,8 @@ def student_detail(
student.is_active = False
student.save()
messages.success(
- request, f'Student "{student.full_name}" Deleted.'
+ request,
+ f'Student "{student.full_name}" Deleted.',
)
return redirect("account_home")
@@ -221,7 +218,8 @@ def student_detail(
def cdc_admin(request, template_name="admin.html"):
if not request.user.is_staff:
messages.error(
- request, "You do not have permission to access this page."
+ request,
+ "You do not have permission to access this page.",
)
return redirect("weallcode-home")
@@ -230,7 +228,7 @@ def cdc_admin(request, template_name="admin.html"):
.annotate(
num_orders=Count("order"),
num_attended=Count(
- Case(When(order__check_in__isnull=False, then=1))
+ Case(When(order__check_in__isnull=False, then=1)),
),
is_future=Case(
When(start_date__gte=timezone.now(), then=1),
@@ -246,7 +244,7 @@ def cdc_admin(request, template_name="admin.html"):
.annotate(
num_orders=Count("meetingorder"),
num_attended=Count(
- Case(When(meetingorder__check_in__isnull=False, then=1))
+ Case(When(meetingorder__check_in__isnull=False, then=1)),
),
is_future=Case(
When(end_date__gte=timezone.now(), then=1),
@@ -262,26 +260,23 @@ def cdc_admin(request, template_name="admin.html"):
total_past_orders = orders.filter(is_active=True)
total_past_orders_count = total_past_orders.count()
total_checked_in_orders = orders.filter(
- is_active=True, check_in__isnull=False
+ is_active=True,
+ check_in__isnull=False,
)
total_checked_in_orders_count = total_checked_in_orders.count()
# Genders
gender_count = list(
- Counter(
- e.student.get_clean_gender() for e in total_checked_in_orders
- ).items()
+ Counter(e.student.get_clean_gender() for e in total_checked_in_orders).items(),
)
gender_count = sorted(
- list(dict(gender_count).items()), key=operator.itemgetter(1)
+ list(dict(gender_count).items()),
+ key=operator.itemgetter(1),
)
# Ages
ages = sorted(
- list(
- e.student.get_age(e.session.start_date)
- for e in total_checked_in_orders
- )
+ list(e.student.get_age(e.session.start_date) for e in total_checked_in_orders),
)
age_count = sorted(
list(dict(list(Counter(ages).items())).items()),
@@ -318,7 +313,8 @@ def cdc_admin(request, template_name="admin.html"):
def session_stats(request, pk, template_name="session_stats.html"):
if not request.user.is_staff:
messages.error(
- request, "You do not have permission to access this page."
+ request,
+ "You do not have permission to access this page.",
)
return redirect("weallcode-home")
@@ -334,7 +330,7 @@ def session_stats(request, pk, template_name="session_stats.html"):
float(current_orders_checked_in.count())
/ float(session_obj.get_active_student_count())
)
- * 100
+ * 100,
)
else:
@@ -343,13 +339,13 @@ def session_stats(request, pk, template_name="session_stats.html"):
# Genders
gender_count = list(
Counter(
- e.student.get_clean_gender()
- for e in session_obj.get_current_orders()
- ).items()
+ e.student.get_clean_gender() for e in session_obj.get_current_orders()
+ ).items(),
)
gender_count = sorted(
- list(dict(gender_count).items()), key=operator.itemgetter(1)
+ list(dict(gender_count).items()),
+ key=operator.itemgetter(1),
)
# Ages
@@ -357,7 +353,7 @@ def session_stats(request, pk, template_name="session_stats.html"):
list(
e.student.get_age(e.session.start_date)
for e in session_obj.get_current_orders()
- )
+ ),
)
age_count = sorted(
@@ -371,11 +367,11 @@ def session_stats(request, pk, template_name="session_stats.html"):
student_ages = []
for order in current_orders_checked_in:
student_ages.append(
- order.student.get_age(order.session.start_date)
+ order.student.get_age(order.session.start_date),
)
average_age = reduce(lambda x, y: x + y, student_ages) / len(
- student_ages
+ student_ages,
)
return render(
@@ -397,7 +393,8 @@ def session_stats(request, pk, template_name="session_stats.html"):
def session_check_in(request, pk, template_name="session_check_in.html"):
if not request.user.is_staff:
messages.error(
- request, "You do not have permission to access this page."
+ request,
+ "You do not have permission to access this page.",
)
return redirect("weallcode-home")
@@ -415,9 +412,7 @@ def session_check_in(request, pk, template_name="session_check_in.html"):
f"{order.guardian.full_name}"
!= request.POST["order_alternate_guardian"]
):
- order.alternate_guardian = request.POST[
- "order_alternate_guardian"
- ]
+ order.alternate_guardian = request.POST["order_alternate_guardian"]
order.save()
else:
@@ -438,22 +433,23 @@ def session_check_in(request, pk, template_name="session_check_in.html"):
.filter(session_id=pk)
.annotate(
num_attended=Count(
- Case(When(student__order__check_in__isnull=False, then=1))
+ Case(When(student__order__check_in__isnull=False, then=1)),
),
num_missed=Count(
- Case(When(student__order__check_in__isnull=True, then=1))
+ Case(When(student__order__check_in__isnull=True, then=1)),
),
)
)
if active_session:
active_orders = orders.filter(is_active=True).order_by(
- "student__first_name"
+ "student__first_name",
)
else:
active_orders = orders.filter(
- is_active=True, check_in__isnull=False
+ is_active=True,
+ check_in__isnull=False,
).order_by("student__first_name")
inactive_orders = orders.filter(is_active=False).order_by("-updated_at")
@@ -469,16 +465,16 @@ def session_check_in(request, pk, template_name="session_check_in.html"):
list(
Counter(
e.student.get_clean_gender() for e in active_orders
- ).items()
- )
- ).items()
+ ).items(),
+ ),
+ ).items(),
),
key=operator.itemgetter(1),
)
# Ages
ages = sorted(
- list(e.student.get_age(e.session.start_date) for e in active_orders)
+ list(e.student.get_age(e.session.start_date) for e in active_orders),
)
age_count = sorted(
@@ -512,11 +508,14 @@ def session_check_in(request, pk, template_name="session_check_in.html"):
@login_required
@never_cache
def session_check_in_mentors(
- request, pk, template_name="session_check_in_mentors.html"
+ request,
+ pk,
+ template_name="session_check_in_mentors.html",
):
if not request.user.is_staff:
messages.error(
- request, "You do not have permission to access this page."
+ request,
+ "You do not have permission to access this page.",
)
return redirect("weallcode-home")
@@ -546,12 +545,13 @@ def session_check_in_mentors(
if active_session:
active_orders = orders.filter(is_active=True).order_by(
- "mentor__user__first_name"
+ "mentor__user__first_name",
)
else:
active_orders = orders.filter(
- is_active=True, check_in__isnull=False
+ is_active=True,
+ check_in__isnull=False,
).order_by("mentor__user__first_name")
inactive_orders = orders.filter(is_active=False).order_by("-updated_at")
@@ -580,7 +580,8 @@ def session_donations(request, pk, template_name="session_donations.html"):
# TODO: we should really turn this into a decorator
if not request.user.is_staff:
messages.error(
- request, "You do not have permission to access this page."
+ request,
+ "You do not have permission to access this page.",
)
return redirect("account_home")
@@ -589,8 +590,9 @@ def session_donations(request, pk, template_name="session_donations.html"):
default_form = DonationForm(initial={"session": session})
default_form.fields["user"].queryset = User.objects.filter(
id__in=Order.objects.filter(session=session).values_list(
- "guardian__user__id", flat=True
- )
+ "guardian__user__id",
+ flat=True,
+ ),
)
form = default_form
@@ -614,18 +616,22 @@ def session_donations(request, pk, template_name="session_donations.html"):
@login_required
@never_cache
def meeting_check_in(
- request, meeting_id, template_name="meeting_check_in.html"
+ request,
+ meeting_id,
+ template_name="meeting_check_in.html",
):
if not request.user.is_staff:
messages.error(
- request, "You do not have permission to access this page."
+ request,
+ "You do not have permission to access this page.",
)
return redirect("account_home")
if request.method == "POST":
if "order_id" in request.POST:
order = get_object_or_404(
- MeetingOrder, id=request.POST["order_id"]
+ MeetingOrder,
+ id=request.POST["order_id"],
)
if order.check_in:
@@ -664,7 +670,8 @@ def meeting_check_in(
def session_announce_mentors(request, pk):
if not request.user.is_staff:
messages.error(
- request, "You do not have permission to access this page."
+ request,
+ "You do not have permission to access this page.",
)
return redirect("home")
@@ -682,14 +689,10 @@ def session_announce_mentors(request, pk):
.format("dddd, MMMM D, YYYY")
),
"class_start_time": (
- arrow.get(session_obj.mentor_start_date)
- .to("local")
- .format("h:mma")
+ arrow.get(session_obj.mentor_start_date).to("local").format("h:mma")
),
"class_end_date": (
- arrow.get(session_obj.end_date)
- .to("local")
- .format("dddd, MMMM D, YYYY")
+ arrow.get(session_obj.end_date).to("local").format("dddd, MMMM D, YYYY")
),
"class_end_time": (
arrow.get(session_obj.end_date).to("local").format("h:mma")
@@ -702,9 +705,7 @@ def session_announce_mentors(request, pk):
"class_location_state": session_obj.location.state,
"class_location_zip": session_obj.location.zip,
"class_additional_info": session_obj.additional_info,
- "class_url": (
- f"{settings.SITE_URL}{session_obj.get_absolute_url()}"
- ),
+ "class_url": (f"{settings.SITE_URL}{session_obj.get_absolute_url()}"),
"class_calendar_url": (
f"{settings.SITE_URL}{session_obj.get_calendar_url()}"
),
@@ -731,8 +732,7 @@ def session_announce_mentors(request, pk):
merge_global_data=merge_global_data,
recipients=recipients,
preheader=(
- "Help us make a huge difference! A brand new class was just"
- " announced."
+ "Help us make a huge difference! A brand new class was just announced."
),
unsub_group_id=settings.SENDGRID_UNSUB_CLASSANNOUNCE,
)
@@ -741,11 +741,12 @@ def session_announce_mentors(request, pk):
session_obj.save()
messages.success(
- request, f"Session announced to {mentors.count()} mentors."
+ request,
+ f"Session announced to {mentors.count()} mentors.",
)
else:
- messages.warning(request, f"Session already announced.")
+ messages.warning(request, "Session already announced.")
return redirect("cdc-admin")
@@ -754,7 +755,8 @@ def session_announce_mentors(request, pk):
def session_announce_guardians(request, pk):
if not request.user.is_staff:
messages.error(
- request, "You do not have permission to access this page."
+ request,
+ "You do not have permission to access this page.",
)
return redirect("home")
@@ -775,9 +777,7 @@ def session_announce_guardians(request, pk):
arrow.get(session_obj.start_date).to("local").format("h:mma")
),
"class_end_date": (
- arrow.get(session_obj.end_date)
- .to("local")
- .format("dddd, MMMM D, YYYY")
+ arrow.get(session_obj.end_date).to("local").format("dddd, MMMM D, YYYY")
),
"class_end_time": (
arrow.get(session_obj.end_date).to("local").format("h:mma")
@@ -790,9 +790,7 @@ def session_announce_guardians(request, pk):
"class_location_state": session_obj.location.state,
"class_location_zip": session_obj.location.zip,
"class_additional_info": session_obj.additional_info,
- "class_url": (
- f"{settings.SITE_URL}{session_obj.get_absolute_url()}"
- ),
+ "class_url": (f"{settings.SITE_URL}{session_obj.get_absolute_url()}"),
"class_calendar_url": (
f"{settings.SITE_URL}{session_obj.get_calendar_url()}"
),
@@ -828,7 +826,8 @@ def session_announce_guardians(request, pk):
session_obj.save()
messages.success(
- request, f"Session announced to {guardians.count()} guardians!"
+ request,
+ f"Session announced to {guardians.count()} guardians!",
)
else:
@@ -867,7 +866,8 @@ def check_system(request):
equipmentType = EquipmentType.objects.get(name="Laptop")
if equipmentType:
equipment, created = Equipment.objects.get_or_create(
- uuid=uuid, defaults={"equipment_type": equipmentType}
+ uuid=uuid,
+ defaults={"equipment_type": equipmentType},
)
# check for blank values of last_system_update.
diff --git a/coderdojochi/settings.py b/coderdojochi/settings.py
index 1c007279..ad596422 100644
--- a/coderdojochi/settings.py
+++ b/coderdojochi/settings.py
@@ -12,11 +12,10 @@
import os
-from django.conf.locale.en import formats as en_formats
-
import dj_database_url
import django_heroku
import environ
+from django.conf.locale.en import formats as en_formats
env = environ.Env()
@@ -63,13 +62,15 @@
SECURE_HSTS_SECONDS = 518400
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains
SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool(
- "DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True
+ "DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS",
+ default=True,
)
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload
SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True)
# https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff
SECURE_CONTENT_TYPE_NOSNIFF = env.bool(
- "DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True
+ "DJANGO_SECURE_CONTENT_TYPE_NOSNIFF",
+ default=True,
)
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter
SECURE_BROWSER_XSS_FILTER = True
@@ -122,7 +123,6 @@
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"django.contrib.redirects.middleware.RedirectFallbackMiddleware",
-
# Add the account middleware:
"allauth.account.middleware.AccountMiddleware",
]
@@ -169,7 +169,7 @@
"PASSWORD": os.environ.get("DB_PASSWORD"),
"HOST": os.environ.get("DB_HOST"),
"PORT": os.environ.get("DB_PORT"),
- }
+ },
}
DATABASES["default"]["ATOMIC_REQUESTS"] = True
@@ -194,19 +194,13 @@
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
- "NAME": (
- "django.contrib.auth.password_validation.MinimumLengthValidator"
- ),
+ "NAME": ("django.contrib.auth.password_validation.MinimumLengthValidator"),
},
{
- "NAME": (
- "django.contrib.auth.password_validation.CommonPasswordValidator"
- ),
+ "NAME": ("django.contrib.auth.password_validation.CommonPasswordValidator"),
},
{
- "NAME": (
- "django.contrib.auth.password_validation.NumericPasswordValidator"
- ),
+ "NAME": ("django.contrib.auth.password_validation.NumericPasswordValidator"),
},
]
@@ -260,7 +254,7 @@
# STORAGES
# ------------------------------------------------------------------------------
# https://django-storages.readthedocs.io/en/latest/#installation
- INSTALLED_APPS += ["storages"] # noqa F405
+ INSTALLED_APPS += ["storages"]
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID")
@@ -290,11 +284,8 @@
# ------------------------------------------------------------------------------
# region http://stackoverflow.com/questions/10390244/
from django.contrib.staticfiles.storage import ManifestFilesMixin
-
- from storages.backends.s3boto3 import ( # noqa E402
- S3Boto3Storage,
- SpooledTemporaryFile,
- )
+ from storages.backends.s3boto3 import S3Boto3Storage
+ from storages.backends.s3boto3 import SpooledTemporaryFile
# ManifestFilesSafeMixin = lambda: ManifestFilesMixin(manifest_strict=False)
# Taken from an issue in django-storages:
@@ -316,7 +307,9 @@ def _save_content(self, obj, content, parameters):
# Upload the object which will auto close the content_autoclose instance
super(CustomS3Storage, self)._save_content(
- obj, content_autoclose, parameters
+ obj,
+ content_autoclose,
+ parameters,
)
# Cleanup if this is fixed upstream our duplicate should always close
@@ -368,9 +361,7 @@ def MediaRootS3BotoStorage():
ACCOUNT_AUTHENTICATION_METHOD = "email"
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_SIGNUP_FORM_CLASS = "coderdojochi.forms.SignupForm"
-SOCIALACCOUNT_ADAPTER = (
- "coderdojochi.social_account_adapter.SocialAccountAdapter"
-)
+SOCIALACCOUNT_ADAPTER = "coderdojochi.social_account_adapter.SocialAccountAdapter"
# Email
diff --git a/coderdojochi/social_account_adapter.py b/coderdojochi/social_account_adapter.py
index 37580e23..52e6698b 100644
--- a/coderdojochi/social_account_adapter.py
+++ b/coderdojochi/social_account_adapter.py
@@ -1,6 +1,5 @@
-from django.contrib.auth import get_user_model
-
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
+from django.contrib.auth import get_user_model
User = get_user_model()
diff --git a/coderdojochi/templatetags/coderdojochi_extras.py b/coderdojochi/templatetags/coderdojochi_extras.py
index 1cfd422f..350394ff 100644
--- a/coderdojochi/templatetags/coderdojochi_extras.py
+++ b/coderdojochi/templatetags/coderdojochi_extras.py
@@ -2,6 +2,7 @@
from django import template
from django.template import Template
+from django.urls import NoReverseMatch
from django.urls import reverse
from coderdojochi.models import Order
@@ -28,7 +29,9 @@ def student_session_order_count(student, session):
@register.simple_tag(takes_context=True)
def student_register_link(context, student, session):
orders = Order.objects.filter(
- student=student, session=session, is_active=True
+ student=student,
+ session=session,
+ is_active=True,
)
url = reverse(
@@ -50,16 +53,20 @@ def student_register_link(context, student, session):
button_msg = "Can't make it"
elif not student.is_within_age_range(
- session.minimum_age, session.maximum_age, session.start_date
+ session.minimum_age,
+ session.maximum_age,
+ session.start_date,
) or not student.is_within_gender_limitation(session.gender_limitation):
button_modifier = "btn-default"
button_additional_attributes = "disabled"
button_tag = "span"
if not student.is_within_age_range(
- session.minimum_age, session.maximum_age, session.start_date
+ session.minimum_age,
+ session.maximum_age,
+ session.start_date,
) and not student.is_within_gender_limitation(
- session.gender_limitation
+ session.gender_limitation,
):
title = "Limited event."
message = (
@@ -75,7 +82,9 @@ def student_register_link(context, student, session):
)
elif not student.is_within_age_range(
- session.minimum_age, session.maximum_age, session.start_date
+ session.minimum_age,
+ session.maximum_age,
+ session.start_date,
):
title = "Age-limited event."
message = (
@@ -91,7 +100,7 @@ def student_register_link(context, student, session):
)
elif not student.is_within_gender_limitation(
- session.gender_limitation
+ session.gender_limitation,
):
if session.gender_limitation == "female":
title = "Girls-only event."
@@ -130,8 +139,7 @@ def menu_is_active(context, pattern_or_urlname, css_class="active"):
if re.search(pattern, context["request"].path):
return css_class
- else:
- return ""
+ return ""
@register.filter(name="phone_number")
diff --git a/coderdojochi/tests/test_mentor_updates.py b/coderdojochi/tests/test_mentor_updates.py
index 8294a010..6aeebc3c 100644
--- a/coderdojochi/tests/test_mentor_updates.py
+++ b/coderdojochi/tests/test_mentor_updates.py
@@ -1,6 +1,6 @@
-from django.test import TransactionTestCase
+from unittest import mock
-import mock
+from django.test import TransactionTestCase
from coderdojochi.models import Mentor
@@ -8,7 +8,7 @@
class TestMentorAvatarUpdates(TransactionTestCase):
@mock.patch("coderdojochi.signals_handlers.EmailMultiAlternatives")
def test_new_mentor_no_avatar(self, mock_email):
- mentor = Mentor.objects.create()
+ Mentor.objects.create()
self.fail()
# @mock.patch('coderdojochi.signals_handlers.EmailMultiAlternatives')
diff --git a/coderdojochi/tests/test_password_session.py b/coderdojochi/tests/test_password_session.py
index f39f0dd9..412295bd 100644
--- a/coderdojochi/tests/test_password_session.py
+++ b/coderdojochi/tests/test_password_session.py
@@ -1,15 +1,11 @@
from django.contrib.auth import get_user_model
from django.http import HttpResponseRedirect
-from django.test import (
- Client,
- TestCase,
-)
+from django.test import Client
+from django.test import TestCase
from django.urls import reverse
-from coderdojochi.factories import (
- PartnerPasswordAccessFactory,
- SessionFactory,
-)
+from coderdojochi.factories import PartnerPasswordAccessFactory
+from coderdojochi.factories import SessionFactory
from coderdojochi.models import PartnerPasswordAccess
User = get_user_model()
@@ -38,7 +34,8 @@ def test_session_password_no_password(self):
def test_session_password_valid_password_unauthed(self):
response = self.client.post(
- self.url, data={"password": self.partner_session.password}
+ self.url,
+ data={"password": self.partner_session.password},
)
self.assertIsInstance(response, HttpResponseRedirect)
@@ -53,14 +50,17 @@ def test_session_password_valid_password_unauthed(self):
def test_session_password_valid_password_authed(self):
user = User.objects.create_user(
- "user", email="email@email.com", password="pass123"
+ "user",
+ email="email@email.com",
+ password="pass123",
)
self.assertTrue(
- self.client.login(email="email@email.com", password="pass123")
+ self.client.login(email="email@email.com", password="pass123"),
)
response = self.client.post(
- self.url, data={"password": self.partner_session.password}
+ self.url,
+ data={"password": self.partner_session.password},
)
self.assertIsInstance(response, HttpResponseRedirect)
@@ -68,7 +68,8 @@ def test_session_password_valid_password_authed(self):
self.assertEqual(response.url, detail_url)
partner_password_access = PartnerPasswordAccess.objects.get(
- session=self.partner_session, user=user
+ session=self.partner_session,
+ user=user,
)
self.assertIsNotNone(partner_password_access)
@@ -99,10 +100,12 @@ def test_redirect_password_unauthed(self):
def test_redirect_password_authed(self):
User.objects.create_user(
- "user", email="email@email.com", password="pass123"
+ "user",
+ email="email@email.com",
+ password="pass123",
)
self.assertTrue(
- self.client.login(email="email@email.com", password="pass123")
+ self.client.login(email="email@email.com", password="pass123"),
)
response = self.client.get(self.url)
@@ -113,14 +116,17 @@ def test_redirect_password_authed(self):
def test_redirect_password_partner_password_access(self):
user = User.objects.create_user(
- "user", email="email@email.com", password="pass123"
+ "user",
+ email="email@email.com",
+ password="pass123",
)
self.assertTrue(
- self.client.login(email="email@email.com", password="pass123")
+ self.client.login(email="email@email.com", password="pass123"),
)
PartnerPasswordAccessFactory.create(
- user=user, session=self.partner_session
+ user=user,
+ session=self.partner_session,
)
response = self.client.get(self.url)
detail_url = reverse("session_password", kwargs=self.url_kwargs)
diff --git a/coderdojochi/urls.py b/coderdojochi/urls.py
index eb5f96f6..72162815 100644
--- a/coderdojochi/urls.py
+++ b/coderdojochi/urls.py
@@ -8,22 +8,18 @@
from django.views.generic import RedirectView
from . import old_views
-from .views import ( # SessionDetailView,
- MeetingCalendarView,
- MeetingDetailView,
- MeetingsView,
- PasswordSessionView,
- SessionCalendarView,
- SessionDetailView,
- SessionSignUpView,
- WelcomeView,
- meeting_announce,
- meeting_sign_up,
-)
-from .views.public import (
- MentorDetailView,
- MentorListView,
-)
+from .views import MeetingCalendarView
+from .views import MeetingDetailView
+from .views import MeetingsView
+from .views import PasswordSessionView
+from .views import SessionCalendarView
+from .views import SessionDetailView
+from .views import SessionSignUpView
+from .views import WelcomeView
+from .views import meeting_announce
+from .views import meeting_sign_up
+from .views.public import MentorDetailView
+from .views.public import MentorListView
admin.autodiscover()
@@ -81,10 +77,10 @@
MeetingCalendarView.as_view(),
name="meeting-calendar",
),
- ]
+ ],
),
),
- ]
+ ],
),
),
]
@@ -137,7 +133,7 @@
old_views.session_donations,
name="donations",
),
- ]
+ ],
),
),
path(
@@ -150,17 +146,19 @@
old_views.meeting_check_in,
name="meeting-check-in",
),
- ]
+ ],
),
),
# Admin Check System
# /admin/checksystem/
path(
- "checksystem/", old_views.check_system, name="check-system"
+ "checksystem/",
+ old_views.check_system,
+ name="check-system",
),
- ]
+ ],
),
- )
+ ),
]
# Sessions
@@ -223,7 +221,7 @@
SessionSignUpView.as_view(),
name="session-sign-up",
),
- ]
+ ],
),
),
]
@@ -257,7 +255,7 @@
old_views.mentor_approve_avatar,
name="mentor-approve-avatar",
),
- ]
+ ],
),
),
]
@@ -291,12 +289,10 @@
path(
"robots.txt",
lambda r: HttpResponse(
- "User-agent: *\nDisallow:\nSitemap: "
- + settings.SITE_URL
- + "/sitemap.xml",
+ "User-agent: *\nDisallow:\nSitemap: " + settings.SITE_URL + "/sitemap.xml",
content_type="text/plain",
),
- )
+ ),
]
# Anymail
@@ -337,5 +333,5 @@
import debug_toolbar
urlpatterns = [
- path("__debug__/", include(debug_toolbar.urls))
+ path("__debug__/", include(debug_toolbar.urls)),
] + urlpatterns
diff --git a/coderdojochi/util.py b/coderdojochi/util.py
index 034fd6d7..d9b2ab0b 100644
--- a/coderdojochi/util.py
+++ b/coderdojochi/util.py
@@ -1,12 +1,11 @@
import logging
+from anymail.message import AnymailMessage
from django.conf import settings
from django.contrib.auth import get_user_model
from django.template.loader import render_to_string
from django.utils import timezone
-from anymail.message import AnymailMessage
-
logger = logging.getLogger(__name__)
User = get_user_model()
@@ -27,7 +26,7 @@ def email(
unsub_group_id=None,
):
if not (subject and template_name and recipients):
- raise NameError()
+ raise ValueError("Missing required parameters: 'subject', 'template_name', and 'recipients' are all required.")
if not isinstance(recipients, list):
raise TypeError("recipients must be a list")
@@ -108,6 +107,20 @@ def email(
user.save()
-def batches(l, n):
- for i in range(0, len(l), n):
- yield l[i : i + n]
+def batches(items, batch_size):
+ """
+ Split a list into smaller batches of a specified size.
+
+ Args:
+ items (list): The list of items to be split into batches
+ batch_size (int): The maximum number of items per batch
+
+ Yields:
+ list: A batch containing up to batch_size items from the original list
+
+ Example:
+ >>> list(batches([1, 2, 3, 4, 5], 2))
+ [[1, 2], [3, 4], [5]]
+ """
+ for start_index in range(0, len(items), batch_size):
+ yield items[start_index : start_index + batch_size]
diff --git a/coderdojochi/views/calendar.py b/coderdojochi/views/calendar.py
index a9778c2a..930841e6 100644
--- a/coderdojochi/views/calendar.py
+++ b/coderdojochi/views/calendar.py
@@ -1,14 +1,11 @@
+import arrow
from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.views.generic import View
-
-import arrow
-from icalendar import (
- Calendar,
- Event,
- vText,
-)
+from icalendar import Calendar
+from icalendar import Event
+from icalendar import vText
class CalendarView(View):
@@ -33,7 +30,8 @@ def get_location(self, request, event_obj):
def get(self, request, *args, **kwargs):
event_obj = get_object_or_404(
- self.event_class, id=kwargs[self.event_kwarg]
+ self.event_class,
+ id=kwargs[self.event_kwarg],
)
cal = Calendar()
@@ -43,9 +41,7 @@ def get(self, request, *args, **kwargs):
event = Event()
- event["uid"] = (
- f"{self.event_type.upper()}{event_obj.id:04}@weallcode.org"
- )
+ event["uid"] = f"{self.event_type.upper()}{event_obj.id:04}@weallcode.org"
event["summary"] = self.get_summary(request, event_obj)
event["dtstart"] = self.get_dtstart(request, event_obj)
event["dtend"] = self.get_dtend(request, event_obj)
@@ -69,11 +65,11 @@ def get(self, request, *args, **kwargs):
# Return the ICS formatted calendar
response = HttpResponse(
- cal.to_ical(), content_type="text/calendar", charset="utf-8"
+ cal.to_ical(),
+ content_type="text/calendar",
+ charset="utf-8",
)
- response["Content-Disposition"] = (
- f"attachment;filename={event_slug}.ics"
- )
+ response["Content-Disposition"] = f"attachment;filename={event_slug}.ics"
return response
diff --git a/coderdojochi/views/guardian/sessions.py b/coderdojochi/views/guardian/sessions.py
index c1af9bdb..39e90e4f 100644
--- a/coderdojochi/views/guardian/sessions.py
+++ b/coderdojochi/views/guardian/sessions.py
@@ -1,16 +1,13 @@
+import arrow
from django.conf import settings
from django.shortcuts import get_object_or_404
from django.views.generic import DetailView
-import arrow
-
-from ...models import (
- Guardian,
- Mentor,
- MentorOrder,
- Order,
- Session,
-)
+from ...models import Guardian
+from ...models import Mentor
+from ...models import MentorOrder
+from ...models import Order
+from ...models import Session
class SessionDetailView(DetailView):
@@ -27,8 +24,9 @@ def get_context_data(self, **kwargs):
) > 0
context["active_mentors"] = Mentor.objects.filter(
id__in=MentorOrder.objects.filter(
- session=self.object, is_active=True
- ).values("mentor__id")
+ session=self.object,
+ is_active=True,
+ ).values("mentor__id"),
)
context["has_students_enrolled"] = Order.objects.filter(
@@ -40,15 +38,15 @@ def get_context_data(self, **kwargs):
NOW = arrow.now()
session_start_time = arrow.get(self.object.start_date).to(
- settings.TIME_ZONE
+ settings.TIME_ZONE,
)
# MAX_DAYS_FOR_PARENTS (30) days before the class start time
open_signup_time = session_start_time.shift(
- days=-settings.MAX_DAYS_FOR_PARENTS
+ days=-settings.MAX_DAYS_FOR_PARENTS,
)
- if NOW < open_signup_time:
+ if open_signup_time > NOW:
context["class_not_open_for_signups"] = True
context["class_time_until_open"] = open_signup_time.humanize(NOW)
diff --git a/coderdojochi/views/meetings.py b/coderdojochi/views/meetings.py
index 045d7099..cb67298a 100644
--- a/coderdojochi/views/meetings.py
+++ b/coderdojochi/views/meetings.py
@@ -1,28 +1,21 @@
import logging
+import arrow
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.http import Http404
-from django.shortcuts import (
- get_object_or_404,
- redirect,
- render,
-)
+from django.shortcuts import get_object_or_404
+from django.shortcuts import redirect
+from django.shortcuts import render
from django.utils import timezone
from django.utils.html import strip_tags
-from django.views.generic import (
- DetailView,
- ListView,
-)
-
-import arrow
+from django.views.generic import DetailView
+from django.views.generic import ListView
-from coderdojochi.models import (
- Meeting,
- MeetingOrder,
- Mentor,
-)
+from coderdojochi.models import Meeting
+from coderdojochi.models import MeetingOrder
+from coderdojochi.models import Mentor
from coderdojochi.util import email
from coderdojochi.views.calendar import CalendarView
@@ -81,11 +74,12 @@ def get_context_data(self, **kwargs):
mentor = get_object_or_404(Mentor, user=self.request.user)
active_meeting_orders = MeetingOrder.objects.filter(
- meeting=self.object, is_active=True
+ meeting=self.object,
+ is_active=True,
)
context["active_meeting_orders"] = active_meeting_orders
context["mentor_signed_up"] = active_meeting_orders.filter(
- mentor=mentor
+ mentor=mentor,
).exists()
return context
@@ -124,7 +118,8 @@ def meeting_sign_up(request, pk, template_name="meeting_sign_up.html"):
mentor = get_object_or_404(Mentor, user=request.user)
meeting_orders = MeetingOrder.objects.filter(
- meeting=meeting_obj, is_active=True
+ meeting=meeting_obj,
+ is_active=True,
)
user_meeting_order = meeting_orders.filter(mentor=mentor)
@@ -136,7 +131,9 @@ def meeting_sign_up(request, pk, template_name="meeting_sign_up.html"):
if request.method == "POST":
if user_signed_up:
meeting_order = get_object_or_404(
- MeetingOrder, meeting=meeting_obj, mentor=mentor
+ MeetingOrder,
+ meeting=meeting_obj,
+ mentor=mentor,
)
meeting_order.is_active = False
meeting_order.save()
@@ -145,15 +142,13 @@ def meeting_sign_up(request, pk, template_name="meeting_sign_up.html"):
else:
if not settings.DEBUG:
- ip = (
- request.META["HTTP_X_FORWARDED_FOR"]
- or request.META["REMOTE_ADDR"]
- )
+ ip = request.META["HTTP_X_FORWARDED_FOR"] or request.META["REMOTE_ADDR"]
else:
ip = request.META["REMOTE_ADDR"]
meeting_order, created = MeetingOrder.objects.get_or_create(
- mentor=mentor, meeting=meeting_obj
+ mentor=mentor,
+ meeting=meeting_obj,
)
meeting_order.ip = ip
@@ -174,9 +169,7 @@ def meeting_sign_up(request, pk, template_name="meeting_sign_up.html"):
.format("dddd, MMMM D, YYYY")
),
"meeting_start_time": (
- arrow.get(meeting_obj.start_date)
- .to("local")
- .format("h:mma")
+ arrow.get(meeting_obj.start_date).to("local").format("h:mma")
),
"meeting_end_date": (
arrow.get(meeting_obj.end_date)
@@ -192,9 +185,7 @@ def meeting_sign_up(request, pk, template_name="meeting_sign_up.html"):
"meeting_location_state": meeting_obj.location.state,
"meeting_location_zip": meeting_obj.location.zip,
"meeting_additional_info": meeting_obj.additional_info,
- "meeting_url": (
- f"{settings.SITE_URL}{meeting_obj.get_absolute_url()}"
- ),
+ "meeting_url": (f"{settings.SITE_URL}{meeting_obj.get_absolute_url()}"),
"meeting_calendar_url": (
f"{settings.SITE_URL}{meeting_obj.get_calendar_url()}"
),
@@ -230,7 +221,8 @@ def meeting_sign_up(request, pk, template_name="meeting_sign_up.html"):
def meeting_announce(request, pk):
if not request.user.is_staff:
messages.error(
- request, "You do not have permission to access this page."
+ request,
+ "You do not have permission to access this page.",
)
return redirect("home")
@@ -250,9 +242,7 @@ def meeting_announce(request, pk):
arrow.get(meeting_obj.start_date).to("local").format("h:mma")
),
"meeting_end_date": (
- arrow.get(meeting_obj.end_date)
- .to("local")
- .format("dddd, MMMM D, YYYY")
+ arrow.get(meeting_obj.end_date).to("local").format("dddd, MMMM D, YYYY")
),
"meeting_end_time": (
arrow.get(meeting_obj.end_date).to("local").format("h:mma")
@@ -263,9 +253,7 @@ def meeting_announce(request, pk):
"meeting_location_state": meeting_obj.location.state,
"meeting_location_zip": meeting_obj.location.zip,
"meeting_additional_info": meeting_obj.additional_info,
- "meeting_url": (
- f"{settings.SITE_URL}{meeting_obj.get_absolute_url()}"
- ),
+ "meeting_url": (f"{settings.SITE_URL}{meeting_obj.get_absolute_url()}"),
"meeting_calendar_url": (
f"{settings.SITE_URL}{meeting_obj.get_calendar_url()}"
),
@@ -291,8 +279,7 @@ def meeting_announce(request, pk):
merge_global_data=merge_global_data,
recipients=recipients,
preheader=(
- "A new meeting has been announced. Come join us for some"
- " amazing fun!"
+ "A new meeting has been announced. Come join us for some amazing fun!"
),
)
@@ -300,7 +287,8 @@ def meeting_announce(request, pk):
meeting_obj.save()
messages.success(
- request, f"Meeting announced to {mentors.count()} mentors."
+ request,
+ f"Meeting announced to {mentors.count()} mentors.",
)
else:
messages.warning(request, "Meeting already announced.")
diff --git a/coderdojochi/views/mentor/sessions.py b/coderdojochi/views/mentor/sessions.py
index 7e9e6756..0b2d887d 100644
--- a/coderdojochi/views/mentor/sessions.py
+++ b/coderdojochi/views/mentor/sessions.py
@@ -1,11 +1,9 @@
from django.shortcuts import get_object_or_404
from django.views.generic import DetailView
-from ...models import (
- Mentor,
- MentorOrder,
- Session,
-)
+from ...models import Mentor
+from ...models import MentorOrder
+from ...models import Session
class SessionDetailView(DetailView):
@@ -23,18 +21,16 @@ def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["mentor_signed_up"] = session_orders.filter(
- mentor=mentor
+ mentor=mentor,
).exists()
- context["spots_remaining"] = (
- session.mentor_capacity - session_orders.count()
- )
+ context["spots_remaining"] = session.mentor_capacity - session_orders.count()
context["account"] = mentor
context["active_mentors"] = Mentor.objects.filter(
id__in=MentorOrder.objects.filter(
session=self.object,
is_active=True,
- ).values("mentor__id")
+ ).values("mentor__id"),
)
return context
diff --git a/coderdojochi/views/profile.py b/coderdojochi/views/profile.py
index 9b465a73..8b39edb6 100644
--- a/coderdojochi/views/profile.py
+++ b/coderdojochi/views/profile.py
@@ -3,22 +3,16 @@
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
-from django.shortcuts import (
- get_object_or_404,
- redirect,
-)
+from django.shortcuts import get_object_or_404
+from django.shortcuts import redirect
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
-from coderdojochi.forms import (
- CDCModelForm,
- MentorForm,
-)
-from coderdojochi.models import (
- Mentor,
- MentorOrder,
-)
+from coderdojochi.forms import CDCModelForm
+from coderdojochi.forms import MentorForm
+from coderdojochi.models import Mentor
+from coderdojochi.models import MentorOrder
logger = logging.getLogger(__name__)
@@ -49,7 +43,8 @@ def get_context_data(self, **kwargs):
# )
past_sessions = orders.filter(
- is_active=True, session__start_date__lte=timezone.now()
+ is_active=True,
+ session__start_date__lte=timezone.now(),
).order_by("session__start_date")
# meeting_orders = MeetingOrder.objects.select_related().filter(mentor=mentor)
@@ -76,7 +71,9 @@ def post(self, request, *args, **kwargs):
form = MentorForm(request.POST, request.FILES, instance=mentor)
user_form = CDCModelForm(
- request.POST, request.FILES, instance=mentor.user
+ request.POST,
+ request.FILES,
+ instance=mentor.user,
)
if form.is_valid() and user_form.is_valid():
@@ -86,5 +83,4 @@ def post(self, request, *args, **kwargs):
return redirect("account_home")
- else:
- messages.error(request, "There was an error. Please try again.")
+ messages.error(request, "There was an error. Please try again.")
diff --git a/coderdojochi/views/public/mentor.py b/coderdojochi/views/public/mentor.py
index af12b95f..c3f49728 100644
--- a/coderdojochi/views/public/mentor.py
+++ b/coderdojochi/views/public/mentor.py
@@ -1,7 +1,5 @@
-from django.views.generic import (
- DetailView,
- ListView,
-)
+from django.views.generic import DetailView
+from django.views.generic import ListView
from ...models import Mentor
diff --git a/coderdojochi/views/public/sessions.py b/coderdojochi/views/public/sessions.py
index 007e9be8..d9e44afa 100644
--- a/coderdojochi/views/public/sessions.py
+++ b/coderdojochi/views/public/sessions.py
@@ -1,11 +1,8 @@
-from django.shortcuts import get_object_or_404
from django.views.generic import DetailView
-from ...models import (
- Mentor,
- MentorOrder,
- Session,
-)
+from ...models import Mentor
+from ...models import MentorOrder
+from ...models import Session
class SessionDetailView(DetailView):
@@ -17,8 +14,9 @@ def get_context_data(self, **kwargs):
context["active_mentors"] = Mentor.objects.filter(
id__in=MentorOrder.objects.filter(
- session=self.object, is_active=True
- ).values("mentor__id")
+ session=self.object,
+ is_active=True,
+ ).values("mentor__id"),
)
return context
diff --git a/coderdojochi/views/sessions.py b/coderdojochi/views/sessions.py
index 7979e1cd..e6d6d3c2 100644
--- a/coderdojochi/views/sessions.py
+++ b/coderdojochi/views/sessions.py
@@ -1,48 +1,33 @@
import logging
+import arrow
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
-from django.shortcuts import (
- get_object_or_404,
- redirect,
- render,
-)
+from django.shortcuts import get_object_or_404
+from django.shortcuts import redirect
+from django.shortcuts import render
from django.urls import reverse
-from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.html import strip_tags
-from django.views.generic import (
- DetailView,
- TemplateView,
- View,
-)
-from django.views.generic.base import RedirectView
-
-import arrow
-
-from coderdojochi.mixins import (
- RoleRedirectMixin,
- RoleTemplateMixin,
-)
-from coderdojochi.models import (
- Guardian,
- Mentor,
- MentorOrder,
- Order,
- PartnerPasswordAccess,
- Session,
- Student,
- guardian,
-)
+from django.views.generic import TemplateView
+from django.views.generic import View
+
+from coderdojochi.mixins import RoleRedirectMixin
+from coderdojochi.mixins import RoleTemplateMixin
+from coderdojochi.models import Guardian
+from coderdojochi.models import Mentor
+from coderdojochi.models import MentorOrder
+from coderdojochi.models import Order
+from coderdojochi.models import PartnerPasswordAccess
+from coderdojochi.models import Session
+from coderdojochi.models import Student
from coderdojochi.util import email
-from . import (
- guardian,
- mentor,
- public,
-)
+from . import guardian
+from . import mentor
+from . import public
from .calendar import CalendarView
logger = logging.getLogger(__name__)
@@ -64,9 +49,7 @@ def session_confirm_mentor(request, session_obj, order):
.format("dddd, MMMM D, YYYY")
),
"class_start_time": (
- arrow.get(session_obj.mentor_start_date)
- .to("local")
- .format("h:mma")
+ arrow.get(session_obj.mentor_start_date).to("local").format("h:mma")
),
"class_end_date": (
arrow.get(session_obj.mentor_end_date)
@@ -83,9 +66,7 @@ def session_confirm_mentor(request, session_obj, order):
"class_location_zip": session_obj.location.zip,
"class_additional_info": session_obj.additional_info,
"class_url": f"{settings.SITE_URL}{session_obj.get_absolute_url()}",
- "class_calendar_url": (
- f"{settings.SITE_URL}{session_obj.get_calendar_url()}"
- ),
+ "class_calendar_url": (f"{settings.SITE_URL}{session_obj.get_calendar_url()}"),
"microdata_start_date": (
arrow.get(session_obj.mentor_start_date).to("local").isoformat()
),
@@ -99,9 +80,7 @@ def session_confirm_mentor(request, session_obj, order):
email(
subject="Mentoring confirmation for {} class".format(
- arrow.get(session_obj.mentor_start_date)
- .to("local")
- .format("MMMM D"),
+ arrow.get(session_obj.mentor_start_date).to("local").format("MMMM D"),
),
template_name="class_confirm_mentor",
merge_global_data=merge_global_data,
@@ -120,21 +99,15 @@ def session_confirm_guardian(request, session_obj, order, student):
"class_title": session_obj.course.title,
"class_description": session_obj.course.description,
"class_start_date": (
- arrow.get(session_obj.start_date)
- .to("local")
- .format("dddd, MMMM D, YYYY")
+ arrow.get(session_obj.start_date).to("local").format("dddd, MMMM D, YYYY")
),
"class_start_time": (
arrow.get(session_obj.start_date).to("local").format("h:mma")
),
"class_end_date": (
- arrow.get(session_obj.end_date)
- .to("local")
- .format("dddd, MMMM D, YYYY")
- ),
- "class_end_time": (
- arrow.get(session_obj.end_date).to("local").format("h:mma")
+ arrow.get(session_obj.end_date).to("local").format("dddd, MMMM D, YYYY")
),
+ "class_end_time": (arrow.get(session_obj.end_date).to("local").format("h:mma")),
"class_location_name": session_obj.location.name,
"class_location_address": session_obj.location.address,
"class_location_city": session_obj.location.city,
@@ -146,9 +119,7 @@ def session_confirm_guardian(request, session_obj, order, student):
"microdata_start_date": (
arrow.get(session_obj.start_date).to("local").isoformat()
),
- "microdata_end_date": (
- arrow.get(session_obj.end_date).to("local").isoformat()
- ),
+ "microdata_end_date": (arrow.get(session_obj.end_date).to("local").isoformat()),
"order_id": order.id,
"online_video_link": session_obj.online_video_link,
"online_video_description": session_obj.online_video_description,
@@ -172,19 +143,23 @@ def get(self, request, *args, **kwargs):
session = get_object_or_404(Session, id=pk)
if session.password and not self.validate_partner_session_access(
- request, pk
+ request,
+ pk,
):
return redirect(reverse("session-password", kwargs=kwargs))
if request.user.is_authenticated:
if request.user.role == "mentor":
return mentor.SessionDetailView.as_view()(
- request, *args, **kwargs
- )
- else:
- return guardian.SessionDetailView.as_view()(
- request, *args, **kwargs
+ request,
+ *args,
+ **kwargs,
)
+ return guardian.SessionDetailView.as_view()(
+ request,
+ *args,
+ **kwargs,
+ )
return public.SessionDetailView.as_view()(request, *args, **kwargs)
def validate_partner_session_access(self, request, pk):
@@ -193,14 +168,16 @@ def validate_partner_session_access(self, request, pk):
if authed_sessions and pk in authed_sessions:
if request.user.is_authenticated:
PartnerPasswordAccess.objects.get_or_create(
- session_id=pk, user=request.user
+ session_id=pk,
+ user=request.user,
)
return True
if request.user.is_authenticated:
try:
PartnerPasswordAccess.objects.get(
- session_id=pk, user_id=request.user.id
+ session_id=pk,
+ user_id=request.user.id,
)
except PartnerPasswordAccess.DoesNotExist:
return False
@@ -221,21 +198,23 @@ def dispatch(self, request, *args, **kwargs):
if request.user.role == "mentor":
session_orders = MentorOrder.objects.filter(
- session=session_obj, is_active=True
+ session=session_obj,
+ is_active=True,
)
kwargs["mentor"] = get_object_or_404(Mentor, user=request.user)
kwargs["user_signed_up"] = session_orders.filter(
- mentor=kwargs["mentor"]
+ mentor=kwargs["mentor"],
).exists()
elif request.user.role == "guardian":
kwargs["guardian"] = get_object_or_404(Guardian, user=request.user)
kwargs["student"] = get_object_or_404(
- Student, id=kwargs["student_id"]
+ Student,
+ id=kwargs["student_id"],
+ )
+ kwargs["user_signed_up"] = kwargs["student"].is_registered_for_session(
+ session_obj
)
- kwargs["user_signed_up"] = kwargs[
- "student"
- ].is_registered_for_session(session_obj)
access_dict = self.check_access(request, *args, **kwargs)
@@ -248,7 +227,9 @@ def dispatch(self, request, *args, **kwargs):
return redirect(access_dict["redirect"])
return super(SessionSignUpView, self).dispatch(
- request, *args, **kwargs
+ request,
+ *args,
+ **kwargs,
)
def check_access(self, request, *args, **kwargs):
@@ -283,7 +264,7 @@ def check_access(self, request, *args, **kwargs):
def student_limitations(self, student, session_obj, user_signed_up):
if not student.is_within_gender_limitation(
- session_obj.gender_limitation
+ session_obj.gender_limitation,
):
return (
"Sorry, this class is limited to"
@@ -291,7 +272,8 @@ def student_limitations(self, student, session_obj, user_signed_up):
)
if not student.is_within_age_range(
- session_obj.minimum_age, session_obj.maximum_age
+ session_obj.minimum_age,
+ session_obj.maximum_age,
):
return (
"Sorry, this class is limited to students between ages"
@@ -326,7 +308,9 @@ def post(self, request, *args, **kwargs):
if user_signed_up:
if mentor:
order = get_object_or_404(
- MentorOrder, mentor=mentor, session=session_obj
+ MentorOrder,
+ mentor=mentor,
+ session=session_obj,
)
elif student:
order = get_object_or_404(
@@ -343,10 +327,7 @@ def post(self, request, *args, **kwargs):
ip = request.META["REMOTE_ADDR"]
if not settings.DEBUG:
- ip = (
- request.META["HTTP_X_FORWARDED_FOR"]
- or request.META["REMOTE_ADDR"]
- )
+ ip = request.META["HTTP_X_FORWARDED_FOR"] or request.META["REMOTE_ADDR"]
if mentor:
order, created = MentorOrder.objects.get_or_create(
@@ -402,7 +383,8 @@ def post(self, request, *args, **kwargs):
# Get from user session or create an empty set
authed_partner_sessions = request.session.get(
- "authed_partner_sessions", []
+ "authed_partner_sessions",
+ [],
)
# Add course session id to user session
@@ -416,7 +398,8 @@ def post(self, request, *args, **kwargs):
if request.user.is_authenticated:
PartnerPasswordAccess.objects.get_or_create(
- session=session_obj, user=request.user
+ session=session_obj,
+ user=request.user,
)
return redirect(session_obj)
@@ -428,17 +411,15 @@ class SessionCalendarView(CalendarView):
event_class = Session
def get_summary(self, request, event_obj):
- return (
- f"We All Code: {event_obj.course.code} - {event_obj.course.title}"
- )
+ return f"We All Code: {event_obj.course.code} - {event_obj.course.title}"
def get_dtstart(self, request, event_obj):
- dtstart = (
- f"{arrow.get(event_obj.start_date).format('YYYYMMDDTHHmmss')}Z"
- )
+ dtstart = f"{arrow.get(event_obj.start_date).format('YYYYMMDDTHHmmss')}Z"
if request.user.is_authenticated and request.user.role == "mentor":
- dtstart = f"{arrow.get(event_obj.mentor_start_date).format('YYYYMMDDTHHmmss')}Z"
+ dtstart = (
+ f"{arrow.get(event_obj.mentor_start_date).format('YYYYMMDDTHHmmss')}Z"
+ )
return dtstart
@@ -461,7 +442,9 @@ def get_location(self, request, event_obj):
try:
mentor = Mentor.objects.get(user=self.request.user)
mentor_signed_up = MentorOrder.objects.filter(
- session=event_obj, is_active=True, mentor=mentor
+ session=event_obj,
+ is_active=True,
+ mentor=mentor,
).exists()
if mentor_signed_up:
diff --git a/coderdojochi/views/welcome.py b/coderdojochi/views/welcome.py
index 8a4a5ce9..84e8b5f8 100644
--- a/coderdojochi/views/welcome.py
+++ b/coderdojochi/views/welcome.py
@@ -3,26 +3,20 @@
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
-from django.shortcuts import (
- get_object_or_404,
- redirect,
- render,
-)
+from django.shortcuts import get_object_or_404
+from django.shortcuts import redirect
+from django.shortcuts import render
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
-from coderdojochi.forms import (
- GuardianForm,
- MentorForm,
- StudentForm,
-)
-from coderdojochi.models import (
- Guardian,
- Meeting,
- Mentor,
- Session,
-)
+from coderdojochi.forms import GuardianForm
+from coderdojochi.forms import MentorForm
+from coderdojochi.forms import StudentForm
+from coderdojochi.models import Guardian
+from coderdojochi.models import Meeting
+from coderdojochi.models import Mentor
+from coderdojochi.models import Session
from coderdojochi.util import email
logger = logging.getLogger(__name__)
@@ -36,17 +30,13 @@ def dispatch(self, request, *args, **kwargs):
next_url = request.GET.get("next")
kwargs["next_url"] = next_url
# Check for redirect condition on mentor, otherwise pass as kwarg
- if (
- getattr(request.user, "role", False) == "mentor"
- and request.method == "GET"
- ):
+ if getattr(request.user, "role", False) == "mentor" and request.method == "GET":
mentor = get_object_or_404(Mentor, user=request.user)
if mentor.first_name:
if next_url:
return redirect(next_url)
- else:
- return redirect("account_home")
+ return redirect("account_home")
kwargs["mentor"] = mentor
return super().dispatch(request, *args, **kwargs)
@@ -72,7 +62,7 @@ def get_context_data(self, **kwargs):
else:
context["add_student"] = True
context["form"] = StudentForm(
- initial={"guardian": guardian.pk}
+ initial={"guardian": guardian.pk},
)
if account.first_name and account.get_students():
@@ -97,8 +87,7 @@ def post(self, request, *args, **kwargs):
return self.update_account(request, account, next_url)
return self.add_student(request, account, next_url)
- else:
- return self.create_new_user(request, user, next_url)
+ return self.create_new_user(request, user, next_url)
def update_account(self, request, account, next_url):
if isinstance(account, Mentor):
@@ -113,11 +102,10 @@ def update_account(self, request, account, next_url):
if next_url:
if "enroll" in request.GET:
next_url = f"{next_url}?enroll=True"
+ elif isinstance(account, Mentor):
+ next_url = "account_home"
else:
- if isinstance(account, Mentor):
- next_url = "account_home"
- else:
- next_url = "welcome"
+ next_url = "welcome"
return redirect(next_url)
return render(
@@ -140,9 +128,7 @@ def add_student(self, request, account, next_url):
messages.success(request, "Student Registered.")
if next_url:
if "enroll" in request.GET:
- next_url = (
- f"{next_url}?enroll=True&student={new_student.id}"
- )
+ next_url = f"{next_url}?enroll=True&student={new_student.id}"
else:
next_url = "welcome"
return redirect(next_url)
@@ -206,7 +192,7 @@ def create_new_user(self, request, user, next_url):
email(
subject="Welcome!",
- template_name=f"welcome_mentor",
+ template_name="welcome_mentor",
merge_global_data=merge_global_data,
recipients=[user.email],
preheader="Welcome to We All Code! Let's get started..",
@@ -214,9 +200,7 @@ def create_new_user(self, request, user, next_url):
else:
# check for next upcoming class
next_class = (
- Session.objects.filter(is_active=True)
- .order_by("start_date")
- .first()
+ Session.objects.filter(is_active=True).order_by("start_date").first()
)
if next_class:
@@ -232,7 +216,7 @@ def create_new_user(self, request, user, next_url):
email(
subject="Welcome!",
- template_name=f"welcome_guardian",
+ template_name="welcome_guardian",
merge_global_data=merge_global_data,
recipients=[user.email],
preheader="Your adventure awaits!",
diff --git a/manage.py b/manage.py
index b3c6b29a..6a428432 100644
--- a/manage.py
+++ b/manage.py
@@ -10,6 +10,6 @@
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
- "forget to activate a virtual environment?"
+ "forget to activate a virtual environment?",
) from exc
execute_from_command_line(sys.argv)
diff --git a/pyproject.toml b/pyproject.toml
index cc353430..f9d7235e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -54,6 +54,7 @@ dependencies = [
# Development & Debugging
"django-debug-toolbar>=5.2.0,<6.0.0",
+ "ruff>=0.8.0,<1.0.0",
# Testing
"django-nose>=1.4.7,<2.0.0",
@@ -75,37 +76,91 @@ dependencies = [
Repository = "https://github.com/weallcode/website"
Homepage = "https://github.com/weallcode/website"
-[tool.black]
-line-length = 79
-target-version = ["py311"]
-include = '\.pyi?$'
-exclude = '''
-/(
- \.eggs
- | \.git
- | \.hg
- | \.mypy_cache
- | \.tox
- | \.venv
- | _build
- | buck-out
- | build
- | dist
- | migrations
-)/
-'''
-
-[tool.isort]
-profile = "black"
-line_length = 79
-multi_line_output = 3
-include_trailing_comma = true
-combine_as_imports = true
-force_grid_wrap = 2
-use_parentheses = true
-remove_redundant_aliases = true
-known_django = "django"
-sections = "FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER"
-skip_gitignore = true
-skip = [".gitignore", ".dockerignore", ".venv"]
-skip_glob = ["**/migrations/*.py"]
+[tool.ruff]
+# Use Ruff's recommended defaults
+target-version = "py311"
+# Exclude a variety of commonly ignored directories.
+extend-exclude = [
+ "*/migrations/*.py",
+ "staticfiles/*",
+]
+
+[tool.ruff.lint]
+select = [
+ "F",
+ "E",
+ "W",
+ "C90",
+ "I",
+ "N",
+ "UP",
+ "YTT",
+ # "ANN", # flake8-annotations: we should support this in the future but 100+ errors atm
+ "ASYNC",
+ "S",
+ "BLE",
+ "FBT",
+ "B",
+ "A",
+ "C4",
+ "DTZ",
+ "T10",
+ "DJ",
+ "EM",
+ "EXE",
+ "FA",
+ 'ISC',
+ "ICN",
+ "G",
+ 'INP',
+ 'PIE',
+ "T20",
+ 'PYI',
+ 'PT',
+ "Q",
+ "RSE",
+ "RET",
+ "SLF",
+ "SLOT",
+ "SIM",
+ "TID",
+ "TC",
+ "INT",
+ # "ARG", # Unused function argument
+ "PTH",
+ "ERA",
+ "PD",
+ "PGH",
+ "PL",
+ "TRY",
+ "FLY",
+ # "NPY",
+ # "AIR",
+ "PERF",
+ # "FURB",
+ # "LOG",
+ "RUF",
+]
+ignore = [
+ "S101", # Use of assert detected https://docs.astral.sh/ruff/rules/assert/
+ "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
+ "SIM102", # sometimes it's better to nest
+ "UP038", # Checks for uses of isinstance/issubclass that take a tuple
+ # of types for comparison.
+ # Deactivated because it can make the code slow:
+ # https://github.com/astral-sh/ruff/issues/7871
+ "COM812", # Trailing comma missing - conflicts with formatter
+]
+# The fixes in extend-unsafe-fixes will require
+# provide the `--unsafe-fixes` flag when fixing.
+extend-unsafe-fixes = [
+ "UP038",
+]
+
+[tool.ruff.lint.isort]
+force-single-line = true
+
+[dependency-groups]
+dev = [
+ "pre-commit>=4.2.0",
+]
diff --git a/tasks.py b/tasks.py
index ecf9c5f2..b0ef42c9 100644
--- a/tasks.py
+++ b/tasks.py
@@ -14,8 +14,7 @@ def release(ctx):
@task(help={"port": "Port to use when serving traffic. Defaults to $PORT."})
def start(ctx, port=env.int("PORT", default=8000)):
ctx.run(
- f"gunicorn coderdojochi.wsgi -w 2 -b 0.0.0.0:{port} --reload"
- " --log-file -"
+ f"gunicorn coderdojochi.wsgi -w 2 -b 0.0.0.0:{port} --reload --log-file -",
)
diff --git a/uv.lock b/uv.lock
index 24ca1777..834ce08e 100644
--- a/uv.lock
+++ b/uv.lock
@@ -84,6 +84,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" },
]
+[[package]]
+name = "cfgv"
+version = "3.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" },
+]
+
[[package]]
name = "charset-normalizer"
version = "3.4.2"
@@ -156,6 +165,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/bb/2aa9b46a01197398b901e458974c20ed107935c26e44e37ad5b0e5511e44/diff_match_patch-20241021-py3-none-any.whl", hash = "sha256:93cea333fb8b2bc0d181b0de5e16df50dd344ce64828226bda07728818936782", size = 43252, upload-time = "2024-10-21T19:41:19.914Z" },
]
+[[package]]
+name = "distlib"
+version = "0.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
+]
+
[[package]]
name = "dj-database-url"
version = "3.0.1"
@@ -435,6 +453,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/26/1c/b909a055be556c11f13cf058cfa0e152f9754d803ff3694a937efe300709/faker-37.4.2-py3-none-any.whl", hash = "sha256:b70ed1af57bfe988cbcd0afd95f4768c51eaf4e1ce8a30962e127ac5c139c93f", size = 1943179, upload-time = "2025-07-15T16:38:23.053Z" },
]
+[[package]]
+name = "filelock"
+version = "3.18.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" },
+]
+
[[package]]
name = "gunicorn"
version = "23.0.0"
@@ -460,6 +487,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/6c/25/b5fc00e85d2dfaf5c806ac8b5f1de072fa11630c5b15b4ae5bbc228abd51/icalendar-6.3.1-py3-none-any.whl", hash = "sha256:7ea1d1b212df685353f74cdc6ec9646bf42fa557d1746ea645ce8779fdfbecdd", size = 242349, upload-time = "2025-05-20T07:42:48.589Z" },
]
+[[package]]
+name = "identify"
+version = "2.6.12"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/88/d193a27416618628a5eea64e3223acd800b40749a96ffb322a9b55a49ed1/identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6", size = 99254, upload-time = "2025-05-23T20:37:53.3Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7a/cd/18f8da995b658420625f7ef13f037be53ae04ec5ad33f9b718240dcfd48c/identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2", size = 99145, upload-time = "2025-05-23T20:37:51.495Z" },
+]
+
[[package]]
name = "idna"
version = "3.10"
@@ -502,6 +538,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/bd/d9/617e6af809bf3a1d468e0d58c3997b1dc219a9a9202e650d30c2fc85d481/mock-5.2.0-py3-none-any.whl", hash = "sha256:7ba87f72ca0e915175596069dbbcc7c75af7b5e9b9bc107ad6349ede0819982f", size = 31617, upload-time = "2025-03-03T12:31:41.518Z" },
]
+[[package]]
+name = "nodeenv"
+version = "1.9.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" },
+]
+
[[package]]
name = "nose"
version = "1.3.7"
@@ -555,6 +600,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" },
]
+[[package]]
+name = "platformdirs"
+version = "4.3.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" },
+]
+
+[[package]]
+name = "pre-commit"
+version = "4.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cfgv" },
+ { name = "identify" },
+ { name = "nodeenv" },
+ { name = "pyyaml" },
+ { name = "virtualenv" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" },
+]
+
[[package]]
name = "psycopg"
version = "3.2.9"
@@ -636,6 +706,23 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
]
+[[package]]
+name = "pyyaml"
+version = "6.0.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" },
+ { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" },
+ { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" },
+ { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" },
+ { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" },
+]
+
[[package]]
name = "requests"
version = "2.32.4"
@@ -651,6 +738,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
]
+[[package]]
+name = "ruff"
+version = "0.12.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/30/cd/01015eb5034605fd98d829c5839ec2c6b4582b479707f7c1c2af861e8258/ruff-0.12.5.tar.gz", hash = "sha256:b209db6102b66f13625940b7f8c7d0f18e20039bb7f6101fbdac935c9612057e", size = 5170722, upload-time = "2025-07-24T13:26:37.456Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d4/de/ad2f68f0798ff15dd8c0bcc2889558970d9a685b3249565a937cd820ad34/ruff-0.12.5-py3-none-linux_armv6l.whl", hash = "sha256:1de2c887e9dec6cb31fcb9948299de5b2db38144e66403b9660c9548a67abd92", size = 11819133, upload-time = "2025-07-24T13:25:56.369Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/fc/c6b65cd0e7fbe60f17e7ad619dca796aa49fbca34bb9bea5f8faf1ec2643/ruff-0.12.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d1ab65e7d8152f519e7dea4de892317c9da7a108da1c56b6a3c1d5e7cf4c5e9a", size = 12501114, upload-time = "2025-07-24T13:25:59.471Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/de/c6bec1dce5ead9f9e6a946ea15e8d698c35f19edc508289d70a577921b30/ruff-0.12.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:962775ed5b27c7aa3fdc0d8f4d4433deae7659ef99ea20f783d666e77338b8cf", size = 11716873, upload-time = "2025-07-24T13:26:01.496Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/16/cf372d2ebe91e4eb5b82a2275c3acfa879e0566a7ac94d331ea37b765ac8/ruff-0.12.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b4cae449597e7195a49eb1cdca89fd9fbb16140c7579899e87f4c85bf82f73", size = 11958829, upload-time = "2025-07-24T13:26:03.721Z" },
+ { url = "https://files.pythonhosted.org/packages/25/bf/cd07e8f6a3a6ec746c62556b4c4b79eeb9b0328b362bb8431b7b8afd3856/ruff-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b13489c3dc50de5e2d40110c0cce371e00186b880842e245186ca862bf9a1ac", size = 11626619, upload-time = "2025-07-24T13:26:06.118Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/c9/c2ccb3b8cbb5661ffda6925f81a13edbb786e623876141b04919d1128370/ruff-0.12.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1504fea81461cf4841778b3ef0a078757602a3b3ea4b008feb1308cb3f23e08", size = 13221894, upload-time = "2025-07-24T13:26:08.292Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/58/68a5be2c8e5590ecdad922b2bcd5583af19ba648f7648f95c51c3c1eca81/ruff-0.12.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7da4129016ae26c32dfcbd5b671fe652b5ab7fc40095d80dcff78175e7eddd4", size = 14163909, upload-time = "2025-07-24T13:26:10.474Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/d1/ef6b19622009ba8386fdb792c0743f709cf917b0b2f1400589cbe4739a33/ruff-0.12.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca972c80f7ebcfd8af75a0f18b17c42d9f1ef203d163669150453f50ca98ab7b", size = 13583652, upload-time = "2025-07-24T13:26:13.381Z" },
+ { url = "https://files.pythonhosted.org/packages/62/e3/1c98c566fe6809a0c83751d825a03727f242cdbe0d142c9e292725585521/ruff-0.12.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dbbf9f25dfb501f4237ae7501d6364b76a01341c6f1b2cd6764fe449124bb2a", size = 12700451, upload-time = "2025-07-24T13:26:15.488Z" },
+ { url = "https://files.pythonhosted.org/packages/24/ff/96058f6506aac0fbc0d0fc0d60b0d0bd746240a0594657a2d94ad28033ba/ruff-0.12.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c47dea6ae39421851685141ba9734767f960113d51e83fd7bb9958d5be8763a", size = 12937465, upload-time = "2025-07-24T13:26:17.808Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/d3/68bc5e7ab96c94b3589d1789f2dd6dd4b27b263310019529ac9be1e8f31b/ruff-0.12.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5076aa0e61e30f848846f0265c873c249d4b558105b221be1828f9f79903dc5", size = 11771136, upload-time = "2025-07-24T13:26:20.422Z" },
+ { url = "https://files.pythonhosted.org/packages/52/75/7356af30a14584981cabfefcf6106dea98cec9a7af4acb5daaf4b114845f/ruff-0.12.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5a4c7830dadd3d8c39b1cc85386e2c1e62344f20766be6f173c22fb5f72f293", size = 11601644, upload-time = "2025-07-24T13:26:22.928Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/67/91c71d27205871737cae11025ee2b098f512104e26ffd8656fd93d0ada0a/ruff-0.12.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:46699f73c2b5b137b9dc0fc1a190b43e35b008b398c6066ea1350cce6326adcb", size = 12478068, upload-time = "2025-07-24T13:26:26.134Z" },
+ { url = "https://files.pythonhosted.org/packages/34/04/b6b00383cf2f48e8e78e14eb258942fdf2a9bf0287fbf5cdd398b749193a/ruff-0.12.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a655a0a0d396f0f072faafc18ebd59adde8ca85fb848dc1b0d9f024b9c4d3bb", size = 12991537, upload-time = "2025-07-24T13:26:28.533Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/b9/053d6445dc7544fb6594785056d8ece61daae7214859ada4a152ad56b6e0/ruff-0.12.5-py3-none-win32.whl", hash = "sha256:dfeb2627c459b0b78ca2bbdc38dd11cc9a0a88bf91db982058b26ce41714ffa9", size = 11751575, upload-time = "2025-07-24T13:26:30.835Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/0f/ab16e8259493137598b9149734fec2e06fdeda9837e6f634f5c4e35916da/ruff-0.12.5-py3-none-win_amd64.whl", hash = "sha256:ae0d90cf5f49466c954991b9d8b953bd093c32c27608e409ae3564c63c5306a5", size = 12882273, upload-time = "2025-07-24T13:26:32.929Z" },
+ { url = "https://files.pythonhosted.org/packages/00/db/c376b0661c24cf770cb8815268190668ec1330eba8374a126ceef8c72d55/ruff-0.12.5-py3-none-win_arm64.whl", hash = "sha256:48cdbfc633de2c5c37d9f090ba3b352d1576b0015bfc3bc98eaf230275b7e805", size = 11951564, upload-time = "2025-07-24T13:26:34.994Z" },
+]
+
[[package]]
name = "s3transfer"
version = "0.13.1"
@@ -739,6 +851,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
]
+[[package]]
+name = "virtualenv"
+version = "20.32.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "distlib" },
+ { name = "filelock" },
+ { name = "platformdirs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a9/96/0834f30fa08dca3738614e6a9d42752b6420ee94e58971d702118f7cfd30/virtualenv-20.32.0.tar.gz", hash = "sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0", size = 6076970, upload-time = "2025-07-21T04:09:50.985Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5c/c6/f8f28009920a736d0df434b52e9feebfb4d702ba942f15338cb4a83eafc1/virtualenv-20.32.0-py3-none-any.whl", hash = "sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56", size = 6057761, upload-time = "2025-07-21T04:09:48.059Z" },
+]
+
[[package]]
name = "we-all-code"
version = "0.0.1"
@@ -775,9 +901,15 @@ dependencies = [
{ name = "mock" },
{ name = "pillow" },
{ name = "psycopg", extra = ["binary"] },
+ { name = "ruff" },
{ name = "sentry-sdk" },
]
+[package.dev-dependencies]
+dev = [
+ { name = "pre-commit" },
+]
+
[package.metadata]
requires-dist = [
{ name = "arrow", specifier = ">=1.3.0,<2.0.0" },
@@ -811,9 +943,13 @@ requires-dist = [
{ name = "mock", specifier = ">=5.2.0,<6.0.0" },
{ name = "pillow", specifier = ">=11.3.0,<12.0.0" },
{ name = "psycopg", extras = ["binary"], specifier = ">=3.2.9,<4.0.0" },
+ { name = "ruff", specifier = ">=0.8.0,<1.0.0" },
{ name = "sentry-sdk", specifier = ">=2.32.0,<3.0.0" },
]
+[package.metadata.requires-dev]
+dev = [{ name = "pre-commit", specifier = ">=4.2.0" }]
+
[[package]]
name = "whitenoise"
version = "6.9.0"
diff --git a/weallcode/admin.py b/weallcode/admin.py
index c6cf533d..fd6309e2 100644
--- a/weallcode/admin.py
+++ b/weallcode/admin.py
@@ -1,11 +1,9 @@
from django.contrib import admin
from django.utils.html import format_html
-from .models import (
- AssociateBoardMember,
- BoardMember,
- StaffMember,
-)
+from .models import AssociateBoardMember
+from .models import BoardMember
+from .models import StaffMember
@admin.register(StaffMember)
diff --git a/weallcode/forms.py b/weallcode/forms.py
index 9265879e..e8be72c2 100644
--- a/weallcode/forms.py
+++ b/weallcode/forms.py
@@ -1,6 +1,5 @@
from django import forms
from django.conf import settings
-
from django_recaptcha.fields import ReCaptchaField
from django_recaptcha.widgets import ReCaptchaV3
diff --git a/weallcode/models/associate_board_member.py b/weallcode/models/associate_board_member.py
index d6771ee1..014d3c1e 100644
--- a/weallcode/models/associate_board_member.py
+++ b/weallcode/models/associate_board_member.py
@@ -1,12 +1,7 @@
-from collections import defaultdict
-from itertools import chain
-
from django.db import models
-from .common import (
- CommonBoardMemberManager,
- CommonInfo,
-)
+from .common import CommonBoardMemberManager
+from .common import CommonInfo
class AssociateBoardMember(CommonInfo):
diff --git a/weallcode/models/board_member.py b/weallcode/models/board_member.py
index 85f3c43e..c314a7a9 100644
--- a/weallcode/models/board_member.py
+++ b/weallcode/models/board_member.py
@@ -1,9 +1,7 @@
from django.db import models
-from .common import (
- CommonBoardMemberManager,
- CommonInfo,
-)
+from .common import CommonBoardMemberManager
+from .common import CommonInfo
class BoardMember(CommonInfo):
diff --git a/weallcode/models/common.py b/weallcode/models/common.py
index e83aa2c6..dcb66b93 100644
--- a/weallcode/models/common.py
+++ b/weallcode/models/common.py
@@ -1,12 +1,8 @@
-from collections import defaultdict
from datetime import date
-from itertools import chain
from django.db import models
-from django.db.models import (
- Case,
- When,
-)
+from django.db.models import Case
+from django.db.models import When
CHAIR = "Chair"
VICE_CHAIR = "Vice Chair"
@@ -73,12 +69,14 @@ def get_sorted(self):
roles = [CHAIR, VICE_CHAIR, TREASURER, SECRETARY, DIRECTOR]
order = Case(
- *[When(role=role, then=pos) for pos, role in enumerate(roles)]
+ *[When(role=role, then=pos) for pos, role in enumerate(roles)],
)
return (
self.get_queryset()
.filter(
- is_active=True, departure_date__isnull=True, role__in=roles
+ is_active=True,
+ departure_date__isnull=True,
+ role__in=roles,
)
.order_by(order, "join_date", "name")
)
diff --git a/weallcode/urls.py b/weallcode/urls.py
index 8b24e2f3..44f8052f 100644
--- a/weallcode/urls.py
+++ b/weallcode/urls.py
@@ -3,18 +3,16 @@
from django.urls import path
from django.views.generic import RedirectView
-from weallcode.views import (
- AssociateBoardView,
- CreditsView,
- HomeView,
- JoinUsView,
- OurStoryView,
- PrivacyView,
- ProgramsSummerCampsView,
- ProgramsView,
- StaticSitemapView,
- TeamView,
-)
+from weallcode.views import AssociateBoardView
+from weallcode.views import CreditsView
+from weallcode.views import HomeView
+from weallcode.views import JoinUsView
+from weallcode.views import OurStoryView
+from weallcode.views import PrivacyView
+from weallcode.views import ProgramsSummerCampsView
+from weallcode.views import ProgramsView
+from weallcode.views import StaticSitemapView
+from weallcode.views import TeamView
sitemaps = {
"static": StaticSitemapView,
@@ -33,7 +31,7 @@
ProgramsSummerCampsView.as_view(),
name="weallcode-programs-summer-camps",
),
- ]
+ ],
),
),
path("team/", TeamView.as_view(), name="weallcode-team"),
@@ -47,7 +45,7 @@
AssociateBoardView.as_view(),
name="weallcode-associate-board",
),
- ]
+ ],
),
),
path("privacy/", PrivacyView.as_view(), name="weallcode-privacy"),
@@ -65,7 +63,8 @@
),
# Redirect /get-involved/ to weallcode-join-us
path(
- "get-involved/", RedirectView.as_view(pattern_name="weallcode-join-us")
+ "get-involved/",
+ RedirectView.as_view(pattern_name="weallcode-join-us"),
),
]
@@ -74,7 +73,7 @@
# Sentry Testing
def trigger_error(request):
- division_by_zero = 1 / 0
+ 1 / 0 # Intentional division by zero for Sentry testing
urlpatterns += [
diff --git a/weallcode/views/associate_board.py b/weallcode/views/associate_board.py
index 2573777d..77a7dc7f 100644
--- a/weallcode/views/associate_board.py
+++ b/weallcode/views/associate_board.py
@@ -8,4 +8,4 @@ class AssociateBoardView(DefaultMetaTags, TemplateView):
template_name = "weallcode/associate_board.html"
url = reverse_lazy("weallcode-associate-board")
- title = f"Join our Associate Board | We All Code"
+ title = "Join our Associate Board | We All Code"
diff --git a/weallcode/views/common.py b/weallcode/views/common.py
index 6c725a29..b0efa92f 100644
--- a/weallcode/views/common.py
+++ b/weallcode/views/common.py
@@ -1,13 +1,14 @@
from django.conf import settings
from django.contrib.auth import get_user_model
-
+from django.shortcuts import render
from meta.views import MetadataMixin
from sentry_sdk import capture_message
+from sentry_sdk import last_event_id
User = get_user_model()
-def page_not_found_view(*args, **kwargs):
+def page_not_found_view(request, exception=None):
print("page_not_found_view")
options = {}
diff --git a/weallcode/views/credits.py b/weallcode/views/credits.py
index 77a8d473..3527ffa8 100644
--- a/weallcode/views/credits.py
+++ b/weallcode/views/credits.py
@@ -1,4 +1,3 @@
-from django.urls import reverse_lazy
from django.views.generic import TemplateView
from .common import DefaultMetaTags
@@ -8,4 +7,4 @@ class CreditsView(DefaultMetaTags, TemplateView):
template_name = "weallcode/credits.html"
# url = reverse_lazy("weallcode-credits")
- title = f"Credits & Attributions | We All Code"
+ title = "Credits & Attributions | We All Code"
diff --git a/weallcode/views/home.py b/weallcode/views/home.py
index b7c4081d..d5ac115e 100644
--- a/weallcode/views/home.py
+++ b/weallcode/views/home.py
@@ -15,7 +15,8 @@ def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
sessions = Session.objects.filter(
- is_active=True, start_date__gte=timezone.now()
+ is_active=True,
+ start_date__gte=timezone.now(),
).order_by("start_date")
if (
diff --git a/weallcode/views/join_us.py b/weallcode/views/join_us.py
index 07260515..b9beeac8 100644
--- a/weallcode/views/join_us.py
+++ b/weallcode/views/join_us.py
@@ -12,7 +12,7 @@ class JoinUsView(DefaultMetaTags, FormView):
url = reverse_lazy("weallcode-join-us")
success_url = reverse_lazy("weallcode-join-us")
- title = f"Join Us | We All Code"
+ title = "Join Us | We All Code"
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
@@ -20,10 +20,7 @@ def form_valid(self, form):
form.send_email()
messages.success(
self.request,
- (
- "Thank you for contacting us! We will respond as soon as"
- " possible."
- ),
+ ("Thank you for contacting us! We will respond as soon as possible."),
)
return super().form_valid(form)
diff --git a/weallcode/views/our_story.py b/weallcode/views/our_story.py
index ebc547ab..aff6366f 100644
--- a/weallcode/views/our_story.py
+++ b/weallcode/views/our_story.py
@@ -8,4 +8,4 @@ class OurStoryView(DefaultMetaTags, TemplateView):
template_name = "weallcode/our_story.html"
url = reverse_lazy("weallcode-our-story")
- title = f"Our Story | We All Code"
+ title = "Our Story | We All Code"
diff --git a/weallcode/views/privacy.py b/weallcode/views/privacy.py
index 9c00bba8..28eb0d5e 100644
--- a/weallcode/views/privacy.py
+++ b/weallcode/views/privacy.py
@@ -8,4 +8,4 @@ class PrivacyView(DefaultMetaTags, TemplateView):
template_name = "weallcode/privacy.html"
url = reverse_lazy("weallcode-privacy")
- title = f"Privacy & Terms | We All Code"
+ title = "Privacy & Terms | We All Code"
diff --git a/weallcode/views/programs.py b/weallcode/views/programs.py
index 2c16e7f9..b3ed2ac7 100644
--- a/weallcode/views/programs.py
+++ b/weallcode/views/programs.py
@@ -1,14 +1,11 @@
+import arrow
from django.conf import settings
from django.urls import reverse_lazy
from django.utils import timezone
from django.views.generic import TemplateView
-import arrow
-
-from coderdojochi.models import (
- Course,
- Session,
-)
+from coderdojochi.models import Course
+from coderdojochi.models import Session
from .common import DefaultMetaTags
@@ -17,20 +14,14 @@ class ProgramsView(DefaultMetaTags, TemplateView):
template_name = "weallcode/programs.html"
url = reverse_lazy("weallcode-programs")
- title = f"Programs | We All Code"
+ title = "Programs | We All Code"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user = self.request.user
- IS_PARENT = (
- True
- if user.is_authenticated and user.role == "guardian"
- else False
- )
- IS_MENTOR = (
- True if user.is_authenticated and user.role == "mentor" else False
- )
+ IS_PARENT = True if user.is_authenticated and user.role == "guardian" else False
+ IS_MENTOR = True if user.is_authenticated and user.role == "mentor" else False
NOW = arrow.now()
# region WEEKEND CLASSES
@@ -56,8 +47,7 @@ def get_context_data(self, **kwargs):
if (
session.mentor_capacity
- and len(session.get_mentor_orders())
- >= session.mentor_capacity
+ and len(session.get_mentor_orders()) >= session.mentor_capacity
):
session.class_status = "Class Full"
else:
@@ -65,13 +55,13 @@ def get_context_data(self, **kwargs):
else:
session.start_time = arrow.get(session.start_date).to(
- settings.TIME_ZONE
+ settings.TIME_ZONE,
)
session.end_time = session.end_date
# MAX_DAYS_FOR_PARENTS (30) days before the class start time
open_signup_time = session.start_time.shift(
- days=-settings.MAX_DAYS_FOR_PARENTS
+ days=-settings.MAX_DAYS_FOR_PARENTS,
)
if IS_PARENT and NOW < open_signup_time:
diff --git a/weallcode/views/programs_summer_camps.py b/weallcode/views/programs_summer_camps.py
index a56a91d2..43ee8acd 100644
--- a/weallcode/views/programs_summer_camps.py
+++ b/weallcode/views/programs_summer_camps.py
@@ -1,6 +1,10 @@
from django.urls import reverse_lazy
+from django.utils import timezone
from django.views.generic import TemplateView
+from coderdojochi.models import Course
+from coderdojochi.models import Session
+
from .common import DefaultMetaTags
@@ -8,7 +12,7 @@ class ProgramsSummerCampsView(DefaultMetaTags, TemplateView):
template_name = "weallcode/programs_summer_camps.html"
url = reverse_lazy("weallcode-programs-summer-camps")
- title = f"Summer Camps | We All Code"
+ title = "Summer Camps | We All Code"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
diff --git a/weallcode/views/team.py b/weallcode/views/team.py
index ef583373..fa433c3e 100644
--- a/weallcode/views/team.py
+++ b/weallcode/views/team.py
@@ -1,6 +1,3 @@
-from collections import defaultdict
-from itertools import chain
-
from django.db.models.aggregates import Count
from django.urls import reverse_lazy
from django.views.generic import TemplateView
@@ -17,7 +14,7 @@ class TeamView(DefaultMetaTags, TemplateView):
template_name = "weallcode/team.html"
url = reverse_lazy("weallcode-team")
- title = f"Team | We All Code"
+ title = "Team | We All Code"
# Instructors
def get_instructors(self, context, volunteers):
@@ -30,7 +27,7 @@ def get_instructors(self, context, volunteers):
# Volunteers
def get_volunteers(self, context, volunteers):
all_volunteers = volunteers.annotate(
- session_count=Count("mentororder")
+ session_count=Count("mentororder"),
).order_by("-user__role", "-session_count")
mentors = []