Skip to content
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
1 change: 0 additions & 1 deletion .github/hooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ function run_checks {

# add pre-commit commands here
run_checks "bandit" bandit pyproject.toml -r
run_checks "isort" isort src --check
run_checks "mypy" mypy src
run_checks "ruff check" ruff check src
run_checks "ruff format" ruff format src
Expand Down
18 changes: 0 additions & 18 deletions .github/workflows/isort.yaml

This file was deleted.

31 changes: 17 additions & 14 deletions src/django_project/blogs/admin.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
# import models
from blogs.models import BlogPlatform, BlogPost, BlogSeries, BlogTag
from __future__ import annotations

from django.contrib import admin

from blogs.models import BlogPlatform, BlogPost, BlogSeries, BlogTag


class BlogPlatformAdmin(admin.ModelAdmin):
list_display: list[str] = ["id", "created_at", "updated_at", "enabled", "name", "website_url"]
search_fields: list[str] = ["id", "name", "website_url"]
list_filter: list[str] = ["enabled"]
list_display: tuple[str, ...] = ("id", "created_at", "updated_at", "enabled", "name", "website_url")
search_fields: tuple[str, ...] = ("id", "name", "website_url")
list_filter: tuple[str, ...] = ("enabled",)


class BlogSeriesAdmin(admin.ModelAdmin):
list_display: list[str] = ["id", "created_at", "updated_at", "name", "description"]
search_fields: list[str] = ["id", "name", "description"]
list_filter: list = []
list_display: tuple[str, ...] = ("id", "created_at", "updated_at", "name", "description")
search_fields: tuple[str, ...] = ("id", "name", "description")
list_filter: tuple[()] = ()


class BlogPostAdmin(admin.ModelAdmin):
list_display: list[str] = [
list_display: tuple[str, ...] = (
"id",
"created_at",
"updated_at",
Expand All @@ -27,15 +30,15 @@ class BlogPostAdmin(admin.ModelAdmin):
"image",
"author",
"series",
]
search_fields: list[str] = ["id", "title", "description", "url", "image", "author"]
list_filter: list[str] = ["platform", "series"]
)
search_fields: tuple[str, ...] = ("id", "title", "description", "url", "image", "author")
list_filter: tuple[str, ...] = ("platform", "series")


class BlogTagAdmin(admin.ModelAdmin):
list_display: list[str] = ["id", "created_at", "updated_at", "value"]
search_fields: list[str] = ["id", "value"]
list_filter: list = []
list_display: tuple[str, ...] = ("id", "created_at", "updated_at", "value")
search_fields: tuple[str, ...] = ("id", "value")
list_filter: tuple[()] = ()


# register models
Expand Down
2 changes: 1 addition & 1 deletion src/django_project/blogs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class BlogTag(HandyHelperBaseModel):
value = models.CharField(max_length=64, unique=True, null=False)

class Meta:
ordering = ["value"]
ordering = ("value",)

def __str__(self) -> str:
return self.value
5 changes: 4 additions & 1 deletion src/django_project/blogs/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from blogs import views
from __future__ import annotations

from django.urls import path
from django.urls.resolvers import URLPattern

from blogs import views

app_name = "blogs"


Expand Down
3 changes: 2 additions & 1 deletion src/django_project/blogs/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from blogs.models import BlogPost
from django.db.models.manager import BaseManager
from django.http import HttpResponse
from django.shortcuts import render
from django.views import View

from blogs.models import BlogPost


class BlogPostListView(View):
def get(self, request) -> HttpResponse:
Expand Down
2 changes: 1 addition & 1 deletion src/django_project/core/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@

@celery_app.task(bind=True)
def debug_task(self, *args, **kwargs):
print("Request: {0!r}".format(self.request))
print(f"Request: {self.request!r}")
2 changes: 2 additions & 0 deletions src/django_project/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
https://docs.djangoproject.com/en/4.2/ref/settings/
"""

from __future__ import annotations

import os
import sys
from pathlib import Path
Expand Down
3 changes: 2 additions & 1 deletion src/django_project/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""

from core.views import HostView, robots_txt
from django.conf import settings
from django.contrib import admin
from django.urls import include, path
from web.views import linkedin_oauth_callback

from core.views import HostView, robots_txt

urlpatterns: list = [
# Django provided URLs
path("console/", admin.site.urls),
Expand Down
48 changes: 47 additions & 1 deletion src/django_project/tests/unit/web/test_linkedin_notifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,30 @@ def test_post_retries_once_after_auth_failure(self):
self.assertEqual(client.access_token, "new-token")
self.assertEqual(mock_post.call_count, 3)

def test_post_does_not_retry_after_forbidden_response(self):
client = self.build_client()

forbidden_response = Mock(status_code=403)
forbidden_response.raise_for_status.side_effect = requests.HTTPError(response=forbidden_response) # type: ignore[name-defined]

with (
patch("web.utilities.notifiers.linkedin.requests.post", return_value=forbidden_response) as mock_post,
patch("web.utilities.notifiers.linkedin.settings"),
self.assertRaises(requests.HTTPError),
):
client.post_organization_post("hello world")

self.assertEqual(client.access_token, "old-token")
self.assertEqual(mock_post.call_count, 1)

def test_uses_configured_api_version_header(self):
with patch("web.utilities.notifiers.linkedin.settings.LINKEDIN_API_VERSION", "202607", create=True):
client = self.build_client()

self.assertEqual(client.headers["LinkedIn-Version"], "202607")

def test_defaults_api_version_header_to_current_year_month(self):
frozen_now = datetime(2026, 7, 22, tzinfo=UTC)
frozen_now = datetime(2026, 8, 6, tzinfo=UTC)

with (
patch("web.utilities.notifiers.linkedin.settings.LINKEDIN_API_VERSION", "", create=True),
Expand All @@ -124,6 +140,36 @@ def test_defaults_api_version_header_to_current_year_month(self):

self.assertEqual(client.headers["LinkedIn-Version"], "202607")

def test_post_retries_once_after_default_version_426(self):
unsupported_version_response = Mock(status_code=426)
unsupported_version_response.raise_for_status.side_effect = requests.HTTPError( # type: ignore[name-defined]
response=unsupported_version_response
)
unsupported_version_response.json.return_value = {
"error": "Unsupported API version",
"message": "The API version you are using has been sunset. Please upgrade to a supported version.",
}

success_response = Mock(status_code=201)
success_response.raise_for_status.return_value = None

frozen_now = datetime(2026, 8, 6, tzinfo=UTC)

with (
patch("web.utilities.notifiers.linkedin.settings.LINKEDIN_API_VERSION", "", create=True),
patch("web.utilities.notifiers.linkedin.timezone.now", return_value=frozen_now),
patch(
"web.utilities.notifiers.linkedin.requests.post",
side_effect=[unsupported_version_response, success_response],
) as mock_post,
):
client = self.build_client()
response = client.post_organization_post("hello world")

self.assertIs(response, success_response)
self.assertEqual(client.headers["LinkedIn-Version"], "202606")
self.assertEqual(mock_post.call_count, 2)

def test_refresh_access_token_updates_db_credential_when_present(self):
credential = DummyCredential()
credential.__class__.objects = DummyCredentialManager(credential)
Expand Down
36 changes: 18 additions & 18 deletions src/django_project/web/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,41 @@


class TagAdmin(admin.ModelAdmin):
list_display = ["id", "value", "created_at", "updated_at"]
search_fields = ["id", "value"]
list_display = ("id", "value", "created_at", "updated_at")
search_fields = ("id", "value")


class LinkAdmin(admin.ModelAdmin):
list_display = ["id", "name", "description", "url", "created_at", "updated_at"]
search_fields = ["id", "name", "description", "url"]
list_display = ("id", "name", "description", "url", "created_at", "updated_at")
search_fields = ("id", "name", "description", "url")


class SocialPlatformAdmin(admin.ModelAdmin):
list_display = ["id", "name", "enabled", "base_url", "created_at", "updated_at"]
search_fields = ["id", "name", "base_url"]
list_filter = ["enabled"]
list_display = ("id", "name", "enabled", "base_url", "created_at", "updated_at")
search_fields = ("id", "name", "base_url")
list_filter = ("enabled",)


class IntegrationCredentialAdmin(admin.ModelAdmin):
list_display = [
list_display = (
"id",
"provider",
"access_token_expires_at",
"refresh_token_expires_at",
"created_at",
"updated_at",
]
search_fields = ["id", "provider"]
)
search_fields = ("id", "provider")


class TechGroupAdmin(admin.ModelAdmin):
list_display = ["id", "name", "description", "enabled", "platform", "icon", "image", "created_at", "updated_at"]
search_fields = ["id", "name", "description", "icon", "image"]
list_filter = ["enabled", "platform"]
list_display = ("id", "name", "description", "enabled", "platform", "icon", "image", "created_at", "updated_at")
search_fields = ("id", "name", "description", "icon", "image")
list_filter = ("enabled", "platform")


class EventAdmin(admin.ModelAdmin):
list_display = [
list_display = (
"id",
"name",
"description",
Expand All @@ -61,8 +61,8 @@ class EventAdmin(admin.ModelAdmin):
"image",
"created_at",
"updated_at",
]
search_fields = [
)
search_fields = (
"id",
"name",
"description",
Expand All @@ -72,8 +72,8 @@ class EventAdmin(admin.ModelAdmin):
"url",
"social_platform_id",
"image",
]
list_filter = ["group"]
)
list_filter = ("group",)


# register models
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.apps import apps
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError

from web.utilities.notifiers.linkedin import LinkedInOrganizationClient


Expand Down
10 changes: 5 additions & 5 deletions src/django_project/web/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class Event(HandyHelperBaseModel):
image = models.ImageField(upload_to="tech_events/", blank=True, null=True)

class Meta:
ordering = ["start_datetime"]
ordering = ("start_datetime",)

def __str__(self) -> str:
return self.name
Expand Down Expand Up @@ -76,7 +76,7 @@ class SocialPlatform(HandyHelperBaseModel):
base_url = models.URLField(blank=True, help_text="base url of provider")

class Meta:
ordering = ["name"]
ordering = ("name",)

def __str__(self) -> str:
return self.name
Expand All @@ -92,7 +92,7 @@ class IntegrationCredential(HandyHelperBaseModel):
refresh_token_expires_at = models.DateTimeField(blank=True, null=True)

class Meta:
ordering = ["provider"]
ordering = ("provider",)

def __str__(self) -> str:
return self.provider
Expand All @@ -104,7 +104,7 @@ class Tag(HandyHelperBaseModel):
value = models.CharField(max_length=64, unique=True, null=False)

class Meta:
ordering = ["value"]
ordering = ("value",)

def __str__(self) -> str:
return self.value
Expand All @@ -128,7 +128,7 @@ class TechGroup(HandyHelperBaseModel):
discord_webhook_url = EncryptedTextField(blank=True, null=True)

class Meta:
ordering = ["name"]
ordering = ("name",)

def __str__(self) -> str:
return self.name
Expand Down
16 changes: 9 additions & 7 deletions src/django_project/web/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.conf import settings
from django.db.models.manager import BaseManager
from django.utils import timezone

from web.models import Event, IntegrationCredential, Link, Tag, TechGroup
from web.utilities.dt_utils import convert_to_pacific
from web.utilities.notifiers.discord import DiscordNotifier
Expand All @@ -27,12 +28,14 @@
get_group_description,
)

logger = logging.getLogger(__name__)


@shared_task(time_limit=30, max_retries=0, name="web.test_task")
def test_task() -> str:
logging.info("test task starting")
logger.info("test task starting")
time.sleep(3)
logging.info("test task completed")
logger.info("test task completed")
return "test task completed!"


Expand Down Expand Up @@ -62,10 +65,9 @@ def ingest_eventbrite_organization_details(group_pk) -> str:

organization_details = get_organization_details(eb_group_id)
description = organization_details["long_description"]["text"]
if description:
if group.description != description:
group.update(description=description)
updated = True
if description and group.description != description:
group.update(description=description)
updated = True
if organization_details.get("website"):
website = organization_details["website"]
if website and not group.links.filter(url=website):
Expand Down Expand Up @@ -104,7 +106,7 @@ def ingest_future_meetup_events(group_pk) -> str:
event_info.setdefault("location_address", "")
event_info.setdefault("map_link", "")
if not event_info.get("name", None):
logging.error(f"error parsing name for event hosted by {group.name}; data = {event_info}")
logger.error("error parsing name for event hosted by %s; data = %s", group.name, event_info)
continue
if event_info["social_platform_id"]:
_, is_new = Event.objects.update_or_create(
Expand Down
Loading
Loading