Skip to content

Commit

Permalink
Merge pull request #1143 from TOMToolkit/1105-add-integration-points-…
Browse files Browse the repository at this point in the history
…for-apps-to-include-partials-on-the-users-profile-page

1105 add integration points for apps to include partials on the users profile page
  • Loading branch information
jchate6 authored Jan 8, 2025
2 parents 2b717bf + 4c13753 commit 559d03f
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 11 deletions.
12 changes: 12 additions & 0 deletions tom_common/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ def ready(self):
plotly_theme = 'plotly_white'

pio.templates.default = plotly_theme

def profile_details(self):
"""
Integration point for adding items to the user profile page.
This method should return a list of dictionaries that include a `partial` key pointing to the path of the html
profile partial. The `context` key should point to the dot separated string path to the templatetag that will
return a dictionary containing new context for the accompanying partial.
Typically, this partial will be a bootstrap card displaying some app specific user data.
"""
return [{'partial': 'tom_common/partials/user_data.html',
'context': 'tom_common.templatetags.user_extras.user_data'}]
23 changes: 23 additions & 0 deletions tom_common/templates/tom_common/partials/app_profiles.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% load user_extras tom_common_extras %}
{% load bootstrap4 %}

<div class="container">
<div class="row">
<div class="col-6">
{% for profile in profiles_to_display %}
{% show_individual_app_profile profile %}

{% comment %}
Start new column halfway through the list of profiles.
{% endcomment %}
{% if forloop.counter == profile_list|length|add:"1"|multiplyby:"0.5"|add:"0" %}
</div>
<div class="col-6">
{% endif %}

{% empty %}
This user has no profile.
{% endfor %}
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% comment %}
This partial template includes another partial from the context specific to a specific App's profile.
This allows the partial to be rendered with only the context specified by the app, without interference from
other app profile contexts.
{% endcomment %}

{% include profile_partial %}
10 changes: 1 addition & 9 deletions tom_common/templates/tom_common/user_profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@ <h3>
{% endif %}
</h3>

<div class="container">
<div class="row">
<div class="col-lg">
{% user_data user %}
</div>
<div class="col-lg">
</div>
</div>
</div>
{% show_app_profiles user %}

{% endblock %}
11 changes: 10 additions & 1 deletion tom_common/templatetags/tom_common_extras.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def verbose_name(instance, field_name):
"""
try:
return instance._meta.get_field(field_name).verbose_name.title()
except FieldDoesNotExist:
except (FieldDoesNotExist, AttributeError):
return field_name.title()


Expand Down Expand Up @@ -108,6 +108,15 @@ def truncate_number(value):
return value


@register.filter
def multiplyby(value, arg):
"""
Multiply the value by a number and return a float.
`{% value|multiplyby:"x.y" %}`
"""
return float(value) * float(arg)


@register.filter
def addstr(arg1, arg2):
"""
Expand Down
51 changes: 51 additions & 0 deletions tom_common/templatetags/user_extras.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import logging
from django import template
from django.contrib.auth.models import Group, User
from django.forms.models import model_to_dict
from django.apps import apps
from django.utils.module_loading import import_string

register = template.Library()
logger = logging.getLogger(__name__)


@register.inclusion_tag('auth/partials/group_list.html', takes_context=True)
Expand Down Expand Up @@ -42,3 +46,50 @@ def user_data(user):
'user_data': user_dict,
'profile_data': profile_dict,
}


@register.inclusion_tag('tom_common/partials/app_profiles.html', takes_context=True)
def show_app_profiles(context, user):
"""
Imports the profile content from relevant apps into the template.
Each profile should be contained in a list of dictionaries in an app's apps.py `profile_details` method.
Each profile dictionary should contain a 'context' key with the path to the context processor class (typically a
templatetag), and a 'partial' key with the path to the html partial template.
FOR EXAMPLE:
[{'partial': 'path/to/partial.html',
'context': 'path/to/context/data/method'}]
"""
profiles_to_display = []
for app in apps.get_app_configs():
try:
profile_details = app.profile_details()
except AttributeError:
continue
if profile_details:
for profile in profile_details:
try:
context_method = import_string(profile['context'])
except ImportError:
logger.warning(f'WARNING: Could not import context for {app.name} profile from '
f'{profile["context"]}.\n'
f'Are you sure you have the right path?')
continue
new_context = context_method(user)
profiles_to_display.append({'partial': profile['partial'], 'context': new_context})

context['user'] = user
context['profiles_to_display'] = profiles_to_display
return context


@register.inclusion_tag('tom_common/partials/include_profile_card.html', takes_context=True)
def show_individual_app_profile(context, profile_data):
"""
An Inclusion tag for setting the unique context for each app's user profile.
"""
for item in profile_data['context']:
context[item] = profile_data['context'][item]
context['profile_partial'] = profile_data['partial']
return context
8 changes: 7 additions & 1 deletion tom_common/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django_comments.models import Comment

from tom_targets.tests.factories import SiderealTargetFactory
from tom_common.templatetags.tom_common_extras import verbose_name
from tom_common.templatetags.tom_common_extras import verbose_name, multiplyby


class TestCommonViews(TestCase):
Expand Down Expand Up @@ -37,6 +37,12 @@ def test_verbose_name(self):
# Check that the verbose name for a non-existent field is returned correctly
self.assertEqual(verbose_name(User, 'definitely_not_a_field'), 'Definitely_Not_A_Field')

def test_multiplyby(self):
# Check that the multiplyby template filter works correctly
self.assertEqual(multiplyby(2, 3), 6)
self.assertEqual(multiplyby(-3, 4), -12)
self.assertEqual(multiplyby(0.5, 5), 2.5)


class TestUserManagement(TestCase):
def setUp(self):
Expand Down

0 comments on commit 559d03f

Please sign in to comment.