Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
97 changes: 97 additions & 0 deletions docs/developer/volunteer_templatetags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Volunteer Template Tags

This document describes the template tags available for rendering volunteer-related content.

## volunteer_tags

The `volunteer_tags` module provides template tags for rendering volunteer profile information.

### Usage

Load the template tags in your template:

```html
{% load volunteer_tags %}
```

### Available Tags

#### `volunteer_languages_badges`

An inclusion tag that renders volunteer languages as Bootstrap badges.

**Usage:**
```html
{% volunteer_languages_badges volunteer_profile %}
```

**With custom CSS classes:**
```html
{% volunteer_languages_badges volunteer_profile "custom-badge-class" %}
```

**Parameters:**
- `volunteer_profile`: A VolunteerProfile instance with a language relationship
- `css_classes` (optional): Custom CSS classes for the badges (default: "badge bg-info text-dark me-1")

**Example:**
```html
<!-- In your template -->
{% load volunteer_tags %}

<div class="languages-section">
<h4>Languages:</h4>
{% volunteer_languages_badges object %}
</div>
```

#### `render_volunteer_languages`

A simple tag that returns volunteer languages as HTML badges.

**Usage:**
```html
{% render_volunteer_languages volunteer_profile %}
```

**With custom CSS classes:**
```html
{% render_volunteer_languages volunteer_profile "my-custom-class" %}
```

**Parameters:**
- `volunteer_profile`: A VolunteerProfile instance with a language relationship
- `css_classes` (optional): Custom CSS classes for the badges (default: "badge bg-info text-dark me-1")

**Example:**
```html
<!-- In your template -->
{% load volunteer_tags %}

<div class="volunteer-info">
Languages: {% render_volunteer_languages volunteer_profile %}
</div>
```

## Migration from Manual Rendering

### Before (duplicated code):
```html
{% for language in object.language.all %}
<span class="badge bg-info text-dark me-1">{{ language.name }}</span>
{% endfor %}
```

### After (using template tag):
```html
{% load volunteer_tags %}
{% volunteer_languages_badges object %}
```

## Benefits

1. **DRY Principle**: Eliminates code duplication across templates
2. **Consistency**: Ensures consistent styling and structure
3. **Maintainability**: Changes to language rendering only need to be made in one place
4. **Flexibility**: Supports custom CSS classes for different use cases
5. **Reusability**: Can be used in any template that needs to display volunteer languages
3 changes: 3 additions & 0 deletions templates/volunteer/templatetags/volunteer_languages.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% for language in languages %}
<span class="{{ css_classes }}">{{ language.name }}</span>
{% endfor %}
5 changes: 2 additions & 3 deletions templates/volunteer/volunteerprofile_detail.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{% extends "portal/base.html" %}
{% load allauth i18n %}
{% load django_bootstrap5 %}
{% load volunteer_tags %}
{% block body %}
{% endblock body %}
{% block content %}
Expand Down Expand Up @@ -72,9 +73,7 @@ <h5 class="card-title mb-0">
Languages:
</div>
<div class="col-md-9">
{% for language in object.language.all %}
<span class="badge bg-info text-dark me-1">{{ language.name }}</span>
{% endfor %}
{% volunteer_languages_badges object %}
</div>
</div>
</div>
Expand Down
5 changes: 2 additions & 3 deletions templates/volunteer/volunteerprofile_review_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{% load allauth i18n %}
{% load django_bootstrap5 %}
{% load static %}
{% load volunteer_tags %}
{% block content %}
<main>
<h1 class="display-5">
Expand Down Expand Up @@ -77,9 +78,7 @@ <h5 class="card-header">
Language
</div>
<div class="col">
{% for language in object.language.all %}
<span class="badge bg-info text-dark me-1">{{ language.name }}</span>
{% endfor %}
{% volunteer_languages_badges object %}
</div>
</div>
</div>
Expand Down
148 changes: 148 additions & 0 deletions tests/volunteer/test_templatetags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
from django.contrib.auth.models import User
from django.template import Context, Template
from django.test import TestCase

from volunteer.models import Language, VolunteerProfile


class VolunteerTemplateTagsTest(TestCase):
"""Test volunteer template tags functionality."""

def setUp(self):
"""Set up test data."""
self.user = User.objects.create_user(
username="testuser", email="[email protected]", password="testpass123"
)

self.volunteer_profile = VolunteerProfile.objects.create(
user=self.user, availability_hours_per_week=10
)

# Create test languages
self.english = Language.objects.create(name="English", code="en")
self.spanish = Language.objects.create(name="Spanish", code="es")
self.french = Language.objects.create(name="French", code="fr")

# Add languages to volunteer profile
self.volunteer_profile.language.add(self.english, self.spanish)

def test_render_volunteer_languages_simple_tag(self):
"""Test the render_volunteer_languages simple tag."""
template = Template(
"""
{% load volunteer_tags %}
{% render_volunteer_languages volunteer_profile %}
"""
)

context = Context({"volunteer_profile": self.volunteer_profile})
rendered = template.render(context)

self.assertIn("English", rendered)
self.assertIn("Spanish", rendered)
self.assertIn("badge bg-info text-dark me-1", rendered)
self.assertNotIn("French", rendered)

def test_render_volunteer_languages_with_custom_css(self):
"""Test the render_volunteer_languages simple tag with custom CSS."""
template = Template(
"""
{% load volunteer_tags %}
{% render_volunteer_languages volunteer_profile "custom-badge" %}
"""
)

context = Context({"volunteer_profile": self.volunteer_profile})
rendered = template.render(context)

self.assertIn("English", rendered)
self.assertIn("Spanish", rendered)
self.assertIn("custom-badge", rendered)
self.assertNotIn("badge bg-info text-dark me-1", rendered)

def test_volunteer_languages_badges_inclusion_tag(self):
"""Test the volunteer_languages_badges inclusion tag."""
template = Template(
"""
{% load volunteer_tags %}
{% volunteer_languages_badges volunteer_profile %}
"""
)

context = Context({"volunteer_profile": self.volunteer_profile})
rendered = template.render(context)

self.assertIn("English", rendered)
self.assertIn("Spanish", rendered)
self.assertIn("badge bg-info text-dark me-1", rendered)
self.assertNotIn("French", rendered)

def test_volunteer_languages_badges_with_custom_css(self):
"""Test the volunteer_languages_badges inclusion tag with custom CSS."""
template = Template(
"""
{% load volunteer_tags %}
{% volunteer_languages_badges volunteer_profile "custom-style" %}
"""
)

context = Context({"volunteer_profile": self.volunteer_profile})
rendered = template.render(context)

self.assertIn("English", rendered)
self.assertIn("Spanish", rendered)
self.assertIn("custom-style", rendered)
self.assertNotIn("badge bg-info text-dark me-1", rendered)

def test_template_tags_with_no_languages(self):
"""Test template tags when volunteer has no languages."""
# Create volunteer with no languages
user2 = User.objects.create_user(
username="testuser2", email="[email protected]", password="testpass123"
)
volunteer_profile2 = VolunteerProfile.objects.create(
user=user2, availability_hours_per_week=5
)

template = Template(
"""
{% load volunteer_tags %}
{% render_volunteer_languages volunteer_profile %}
"""
)

context = Context({"volunteer_profile": volunteer_profile2})
rendered = template.render(context)

# Should render empty string when no languages
self.assertEqual(rendered.strip(), "")

def test_template_tags_with_none_profile(self):
"""Test template tags when volunteer_profile is None."""
template = Template(
"""
{% load volunteer_tags %}
{% render_volunteer_languages volunteer_profile %}
"""
)

context = Context({"volunteer_profile": None})
rendered = template.render(context)

# Should render empty string when profile is None
self.assertEqual(rendered.strip(), "")

def test_template_tags_with_missing_profile(self):
"""Test template tags when volunteer_profile is not in context."""
template = Template(
"""
{% load volunteer_tags %}
{% render_volunteer_languages volunteer_profile %}
"""
)

context = Context({})
rendered = template.render(context)

# Should render empty string when profile is missing
self.assertEqual(rendered.strip(), "")
Empty file.
56 changes: 56 additions & 0 deletions volunteer/templatetags/volunteer_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from django import template
from django.utils.safestring import mark_safe

register = template.Library()


@register.simple_tag
def render_volunteer_languages(
volunteer_profile, css_classes="badge bg-info text-dark me-1"
):
"""
Renders volunteer languages as badges.

Args:
volunteer_profile: VolunteerProfile instance with language relationship
css_classes: CSS classes for the badge styling (optional)

Returns:
Safe HTML string with language badges
"""
if not volunteer_profile or not hasattr(volunteer_profile, "language"):
return ""

languages = volunteer_profile.language.all()
if not languages:
return ""

badge_html = []
for language in languages:
badge_html.append(f'<span class="{css_classes}">{language.name}</span>')

return mark_safe(" ".join(badge_html))


@register.inclusion_tag("volunteer/templatetags/volunteer_languages.html")
def volunteer_languages_badges(
volunteer_profile, css_classes="badge bg-info text-dark me-1"
):
"""
Inclusion tag to render volunteer languages using a template.

Args:
volunteer_profile: VolunteerProfile instance with language relationship
css_classes: CSS classes for the badge styling (optional)

Returns:
Context dictionary for the template
"""
languages = []
if volunteer_profile and hasattr(volunteer_profile, "language"):
languages = volunteer_profile.language.all()

return {
"languages": languages,
"css_classes": css_classes,
}