Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ DJANGO_SUPERUSER_EMAIL=benefits-admin@calitp.org
# Django
DJANGO_STORAGE_DIR=.
DJANGO_DB_FIXTURES="benefits/core/migrations/local_fixtures.json"
DJANGO_DEBUG_TOOLBAR=false
# DJANGO_LOCAL_PORT=

USE_POSTGRES=false
Expand Down
4 changes: 3 additions & 1 deletion benefits/core/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ def agency(request):

def active_agencies(request):
"""Context processor adds some information about all active agencies to the request context."""
agencies = models.TransitAgency.all_active()
agencies = models.TransitAgency.all_active().select_related(
"transit_processor_config", "transit_processor_config__littlepayconfig", "transit_processor_config__switchioconfig"
)

return {"active_agencies": [_agency_context(agency) for agency in agencies]}

Expand Down
2 changes: 1 addition & 1 deletion benefits/core/models/transit.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def group_agencies(self, only_active=True):
"""

agencies_in_group = (
TransitAgency.objects.filter(transitagencygroup__in=list(self.transitagencygroup_set.all()))
TransitAgency.objects.filter(transitagencygroup__in=self.transitagencygroup_set.all())
.distinct()
.exclude(pk=self.pk)
)
Expand Down
24 changes: 20 additions & 4 deletions benefits/core/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,17 @@

def agency(request):
"""Get the agency from the request's session, or None"""
agency_id = request.session.get(_AGENCY)
if not agency_id:
return None

if getattr(request, "_cached_agency", None) and request._cached_agency.id == agency_id:
return request._cached_agency
try:
return models.TransitAgency.by_id(request.session[_AGENCY])
except (KeyError, models.TransitAgency.DoesNotExist):
agency = models.TransitAgency.by_id(agency_id)
request._cached_agency = agency

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a more "Django" way to do this caching? Attaching a random prop on to the request feels a little hacky and like it would be easy to get out of control.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completely agree, I had the same feeling. I'll do a bit more searching for a cleaner, more "Django" way to do this. I think it's worth the effort it since it does lower the duplicated queries by a good bit especially on the /eligibility part.

return agency
except models.TransitAgency.DoesNotExist:
return None


Expand Down Expand Up @@ -124,9 +132,17 @@ def enrollment_reenrollment(request):

def flow(request) -> models.EnrollmentFlow | None:
"""Get the EnrollmentFlow from the request's session, or None"""
flow_id = request.session.get(_FLOW)
if not flow_id:
return None

if getattr(request, "_cached_flow", None) and request._cached_flow.id == flow_id:
return request._cached_flow
try:
return models.EnrollmentFlow.by_id(request.session[_FLOW])
except (KeyError, models.EnrollmentFlow.DoesNotExist):
flow = models.EnrollmentFlow.by_id(flow_id)
request._cached_flow = flow

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question as above

return flow
except models.EnrollmentFlow.DoesNotExist:
return None


Expand Down
17 changes: 17 additions & 0 deletions benefits/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ def _filter_empty(ls):
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.environ.get("DJANGO_DEBUG", "false").lower() == "true"

# SECURITY WARNING: don't run with debug_toolbar turned on in production!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For extra caution?

Suggested change
# SECURITY WARNING: don't run with debug_toolbar turned on in production!
# SECURITY WARNING: don't run with debug_toolbar turned on in any non-local environment!

I also wonder if it'd be nice to move this down with the activation code? Everything else up here are core Django settings.

DEBUG_TOOLBAR = os.environ.get("DJANGO_DEBUG_TOOLBAR", "false").lower() == "true"
Comment thread
thekaveman marked this conversation as resolved.

ALLOWED_HOSTS = _filter_empty(os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost").split(","))


Expand Down Expand Up @@ -107,6 +110,20 @@ def RUNTIME_ENVIRONMENT():
if DEBUG:
MIDDLEWARE.append("benefits.core.middleware.DebugSession")

if DEBUG_TOOLBAR:
INSTALLED_APPS.append("debug_toolbar")
MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware")
INTERNAL_IPS = ["127.0.0.1"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this has no effect when overriding SHOW_TOOLBAR_CALLBACK completely. If I comment it out or change the IP, DDT still appears.

Suggested change
INTERNAL_IPS = ["127.0.0.1"]

If we weren't using Docker, I think we could not override SHOW_TOOLBAR_CALLBACK and rely only on INTERNAL_IPS, but Docker makes that complicated. There's a utility to try and help with that, but it's not well documented and apparently doesn't work with Docker Compose.


def show_toolbar(request):
# Show the toolbar when in local development mode
return DEBUG

DEBUG_TOOLBAR_CONFIG = {
"SHOW_TOOLBAR_CALLBACK": show_toolbar,
}
Comment on lines +118 to +124

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may not be worth the readability tradeoff, but you could simplify this with a lambda function:

Suggested change
def show_toolbar(request):
# Show the toolbar when in local development mode
return DEBUG
DEBUG_TOOLBAR_CONFIG = {
"SHOW_TOOLBAR_CALLBACK": show_toolbar,
}
DEBUG_TOOLBAR_CONFIG = {
"SHOW_TOOLBAR_CALLBACK": lambda request: DEBUG,
}



HEALTHCHECK_USER_AGENTS = _filter_empty(os.environ.get("HEALTHCHECK_USER_AGENTS", "").split(","))

CSRF_COOKIE_AGE = None
Expand Down
6 changes: 6 additions & 0 deletions benefits/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ def trigger_csrf(request):
urlpatterns.append(path("test500/", trigger_500))
urlpatterns.append(path("testcsrf/", trigger_csrf))

if settings.DEBUG_TOOLBAR:
from debug_toolbar.toolbar import debug_toolbar_urls

urlpatterns.extend(debug_toolbar_urls())


if settings.RUNTIME_ENVIRONMENT() in (settings.RUNTIME_ENVS.LOCAL, settings.RUNTIME_ENVS.DEV):
# simple route to read a pre-defined "secret"
# this "secret" does not contain sensitive information
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dev = [
"flake8",
"isort",
"pre-commit",
"django-debug-toolbar"
Comment thread
thekaveman marked this conversation as resolved.
]
test = [
"coverage",
Expand Down
46 changes: 46 additions & 0 deletions tests/pytest/core/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,29 @@
from benefits.routes import routes


@pytest.mark.django_db
def test_agency_does_not_exist(app_request):
app_request.session[session._AGENCY] = -1
assert not session.agency(app_request)


@pytest.mark.django_db
def test_agency_cache(model_TransitAgency, app_request):
session.update(app_request, agency=model_TransitAgency)
assert not hasattr(app_request, "_cached_agency")

# set _cached_agency
first_call = session.agency(app_request)

assert first_call == model_TransitAgency
assert hasattr(app_request, "_cached_agency")
assert app_request._cached_agency == model_TransitAgency

second_call = session.agency(app_request)

assert second_call == model_TransitAgency


@pytest.mark.django_db
def test_active_agency_False(app_request, model_TransitAgency_inactive):
session.update(app_request, agency=None)
Expand Down Expand Up @@ -132,6 +155,29 @@ def test_enrollment_reenrollment(app_request, model_EnrollmentFlow_supports_expi
assert session.enrollment_reenrollment(app_request) == expected_reenrollment


@pytest.mark.django_db
def test_flow_does_not_exist(app_request):
app_request.session[session._FLOW] = -1
assert not session.flow(app_request)


@pytest.mark.django_db
def test_flow_cache(model_EnrollmentFlow, app_request):
session.update(app_request, flow=model_EnrollmentFlow)
assert not hasattr(app_request, "_cached_flow")

# set _cached_flow
first_call = session.flow(app_request)

assert first_call == model_EnrollmentFlow
assert hasattr(app_request, "_cached_flow")
assert app_request._cached_flow == model_EnrollmentFlow

second_call = session.flow(app_request)

assert second_call == model_EnrollmentFlow


@pytest.mark.django_db
def test_language_default(app_request):
assert session.language(app_request) == "en"
Expand Down
Loading