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

[#2067] Upcoming Teaching Opportunities on instructor page #2127

6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
repos:
- repo: https://github.com/psf/black
rev: 20.8b1
rev: 22.1.0
hooks:
- id: black
language_version: python3
exclude: (migrations/|urls\.py)
- repo: https://github.com/pycqa/flake8
rev: 3.9.2
rev: 4.0.1
hooks:
- id: flake8
exclude: (migrations/|urls\.py)
- repo: https://github.com/pycqa/isort
rev: 5.6.4
rev: 5.10.1
hooks:
- id: isort
args: ["--profile", "black", "--filter-files"]
22 changes: 22 additions & 0 deletions amy/communityroles/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from datetime import date

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
Expand Down Expand Up @@ -69,3 +71,23 @@ def __str__(self) -> str:

def get_absolute_url(self):
return reverse("communityrole_details", kwargs={"pk": self.pk})

def is_active(self) -> bool:
"""Determine if a community role is considered active.

Rules for INACTIVE:
1. `inactivation` is provided, or...
2. End is provided and it's <= today, or...
3. Start is provided and it's > today, or...
4. Both start and end are provided, and today is NOT between them.

Otherwise by default it's ACTIVE."""
today = date.today()
if (
self.inactivation is not None
or (self.end and self.end <= today)
or (self.start and self.start > today)
or (self.start and self.end and not (self.start <= today < self.end))
):
return False
return True
Empty file.
16 changes: 16 additions & 0 deletions amy/communityroles/templatetags/communityroles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import Optional

from django import template

from communityroles.models import CommunityRole
from workshops.models import Person

register = template.Library()


@register.simple_tag
def get_community_role(person: Person, role_name: str) -> Optional[CommunityRole]:
try:
return CommunityRole.objects.get(person=person, config__name=role_name)
except CommunityRole.DoesNotExist:
return None
62 changes: 62 additions & 0 deletions amy/communityroles/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from datetime import date, timedelta
from typing import Optional

from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from django.urls.base import reverse
Expand Down Expand Up @@ -77,3 +80,62 @@ def test_get_absolute_url(self):
self.assertEqual(
url, reverse("communityrole_details", args=[self.community_role.pk])
)

def test_is_active(self):
# Arrange
person = Person(personal="Test", family="User", email="[email protected]")
config = CommunityRoleConfig(
name="test_config",
display_name="Test Config",
link_to_award=False,
link_to_membership=False,
additional_url=False,
)
inactivation = CommunityRoleInactivation(name="test inactivation")
today = date.today()
yesterday = today - timedelta(days=1)
tomorrow = today + timedelta(days=1)
data: list[
tuple[
Optional[CommunityRoleInactivation], # role.inactivation
Optional[date], # role.start
Optional[date], # role.end
bool, # expected result
]
] = [
# cases when we have inactivation set: always False
(inactivation, None, None, False),
(inactivation, yesterday, tomorrow, False),
(inactivation, yesterday, None, False),
(inactivation, None, tomorrow, False),
# cases when no start/no end
(None, None, None, True),
# cases when both start and end are available
(None, yesterday, tomorrow, True),
(None, tomorrow, yesterday, False),
(None, today, tomorrow, True),
(None, yesterday, today, False),
# cases when only start is provided
(None, tomorrow, None, False),
(None, today, None, True),
(None, yesterday, None, True),
# cases when only end is provided
(None, None, tomorrow, True),
(None, None, today, False),
(None, None, yesterday, False),
]
for inactivation, start, end, expected in data:
with self.subTest(inactivation=inactivation, start=start, end=end):
community_role = CommunityRole(
config=config,
person=person,
inactivation=inactivation,
start=start,
end=end,
)

# Act
result = community_role.is_active()

# Assert
self.assertEqual(result, expected)
70 changes: 70 additions & 0 deletions amy/communityroles/tests/test_templatetags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from django.test import TestCase

from communityroles.models import CommunityRole, CommunityRoleConfig
from communityroles.templatetags.communityroles import get_community_role
from workshops.models import Person


class TestCommunityRolesTemplateTags(TestCase):
def test_get_community_role(self):
# Arrange
person = Person.objects.create(
personal="Test", family="User", email="[email protected]"
)
role_name = "instructor"
config = CommunityRoleConfig.objects.create(
name=role_name,
display_name="Instructor",
link_to_award=False,
link_to_membership=False,
additional_url=False,
)
role_orig = CommunityRole.objects.create(config=config, person=person)
# Act
role_found = get_community_role(person, role_name)
# Assert
self.assertEqual(role_orig, role_found)

def test_get_community_role__config_not_found(self):
# Arrange
person = Person.objects.create(
personal="Test", family="User", email="[email protected]"
)
role_name = "instructor"
config = CommunityRoleConfig.objects.create(
name=role_name,
display_name="Instructor",
link_to_award=False,
link_to_membership=False,
additional_url=False,
)
CommunityRole.objects.create(config=config, person=person)
# Act
role_found = get_community_role(person, "fake_role")
# Assert
self.assertEqual(role_found, None)

def test_get_community_role__person_not_found(self):
# Arrange
person = Person.objects.create(
personal="Test", family="User", email="[email protected]"
)
fake_person = Person.objects.create(
personal="Fake",
family="Person",
email="[email protected]",
username="fake_user",
)
role_name = "instructor"
config = CommunityRoleConfig.objects.create(
name=role_name,
display_name="Instructor",
link_to_award=False,
link_to_membership=False,
additional_url=False,
)
CommunityRole.objects.create(config=config, person=person)
# Act
role_found = get_community_role(fake_person, role_name)
# Assert
self.assertEqual(role_found, None)
6 changes: 3 additions & 3 deletions amy/consents/tests/test_action_required_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def test_logged_in_user(self):
the required terms is redirected to the form."""
urls = [
reverse("admin-dashboard"),
reverse("trainee-dashboard"),
reverse("instructor-dashboard"),
]

# ensure we're logged in
Expand All @@ -150,7 +150,7 @@ def test_logged_in_user(self):
def test_no_more_redirects_after_agreement(self):
"""Ensure user is no longer forcefully redirected to accept the
required terms."""
url = reverse("trainee-dashboard")
url = reverse("instructor-dashboard")

# ensure we're logged in
self.client.force_login(self.neville)
Expand Down Expand Up @@ -215,7 +215,7 @@ def test_old_terms_do_not_affect_terms_middleware(self):
"""
urls = [
reverse("admin-dashboard"),
reverse("trainee-dashboard"),
reverse("instructor-dashboard"),
]
harry = Person.objects.create(
personal="Harry",
Expand Down
68 changes: 68 additions & 0 deletions amy/dashboard/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from django.db.models import F, QuerySet
import django_filters as filters

from recruitment.models import InstructorRecruitment
from workshops.filters import AMYFilterSet


class UpcomingTeachingOpportunitiesFilter(AMYFilterSet):
status = filters.ChoiceFilter(
choices=(
("online", "Online only"),
("inperson", "Inperson only"),
),
empty_label="Any",
label="Online/inperson",
method="filter_status",
)

order_by = filters.OrderingFilter(
fields=("event__start",),
choices=(
("event__start", "Event start"),
("-event__start", "Event start (descending)"),
("proximity", "Closer to my airport"),
("-proximity", "Further away from my airport"),
),
method="filter_order_by",
)

class Meta:
model = InstructorRecruitment
fields = [
"status",
]

def filter_status(self, queryset: QuerySet, name: str, value: str) -> QuerySet:
"""Filter recruitments based on the event (online/inperson) status."""
if value == "online":
return queryset.filter(event__tags__name="online")
elif value == "inperson":
return queryset.exclude(event__tags__name="online")
else:
return queryset

def filter_order_by(self, queryset: QuerySet, name: str, values: list) -> QuerySet:
"""Order entries by proximity to user's airport."""
try:
latitude: float = self.request.user.airport.latitude
except AttributeError:
latitude = 0.0

try:
longitude: float = self.request.user.airport.longitude
except AttributeError:
longitude = 0.0

# `0.0` is neutral element for this equation, so even if user doesn't have the
# airport specified, the sorting should still work
distance = (F("event__latitude") - latitude) ** 2 + (
F("event__longitude") - longitude
) ** 2

if values == ["proximity"]:
return queryset.annotate(distance=distance).order_by("distance")
elif values == ["-proximity"]:
return queryset.annotate(distance=distance).order_by("-distance")
else:
return queryset.order_by(*values)
Loading