Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1105 add integration points for apps to include partials on the users profile page #1143

Merged
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
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
Loading