Skip to content

Commit

Permalink
Merge pull request #4 from mozmeao/refactor-link-generation-to-be-a-p…
Browse files Browse the repository at this point in the history
…ython-api

Move the link generation out of the view, to make it more reusable from Python
  • Loading branch information
stevejalim authored Nov 13, 2024
2 parents 2a4b004 + d234ec9 commit d829af8
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 79 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "wagtaildraftsharing"
version = "0.1.2"
version = "0.1.3"
description = "Share wagtail drafts with private URLs."
readme = "README.md"
requires-python = ">=3.9"
Expand Down
36 changes: 36 additions & 0 deletions wagtaildraftsharing/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,43 @@
from django.db import models
from django.urls import reverse
from django.utils.html import format_html
from django.utils.timezone import timedelta
from wagtail.log_actions import log

from wagtaildraftsharing.actions import WAGTAILDRAFTSHARING_CREATE_SHARING_LINK
from wagtaildraftsharing.utils import tz_aware_utc_now

from . import settings as draftsharing_settings

max_age = draftsharing_settings.WAGTAILDRAFTSHARING_MAX_AGE


class WagtaildraftsharingLinkManager(models.Manager):
def get_or_create_for_revision(self, *, revision, user):
key = uuid.uuid4()
if max_age > 0:
active_until = tz_aware_utc_now() + timedelta(seconds=max_age)
else:
active_until = None
sharing_link, created = WagtaildraftsharingLink.objects.get_or_create(
revision=revision,
defaults={
"key": key,
"created_by": user,
"active_until": active_until,
},
)
if created:
log(
instance=revision.content_object,
action=WAGTAILDRAFTSHARING_CREATE_SHARING_LINK,
user=user,
revision=revision,
data={"revision": revision.id},
)

return sharing_link


class WagtaildraftsharingLink(models.Model):
key = models.UUIDField(
Expand Down Expand Up @@ -36,6 +70,8 @@ class WagtaildraftsharingLink(models.Model):
editable=False,
)

objects = WagtaildraftsharingLinkManager()

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

Expand Down
62 changes: 61 additions & 1 deletion wagtaildraftsharing/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,71 @@
import datetime
from textwrap import dedent
from unittest.mock import patch

import wagtail
from django.contrib.auth.models import User
from django.test import TestCase
from django.utils.timezone import is_aware
from freezegun import freeze_time
from wagtail_factories import PageFactory

from ..models import WagtaildraftsharingLink
import wagtaildraftsharing.models
from wagtaildraftsharing.models import WagtaildraftsharingLink

FROZEN_TIME_ISOFORMATTED = "2024-01-02 12:34:56.123456+00:00"


class TestWagtaildraftsharingLinkManager(TestCase):
def setUp(self):
self.test_user = User.objects.create(
username="test", email="[email protected]"
)

def create_revision(self):
page = PageFactory()

# create the first revision
page.save_revision().publish()

old_title = page.title
new_title = f"New {old_title}"
page.title = new_title

# create the second revision with a new title
page.save_revision().publish()

page.refresh_from_db()
earliest_revision = page.revisions.earliest("created_at")
return earliest_revision

@freeze_time(FROZEN_TIME_ISOFORMATTED)
def test_create_sharing_link_view__max_age_from_settings(self):
frozen_time = datetime.datetime.fromisoformat(FROZEN_TIME_ISOFORMATTED)

# Ensure we've got a level playing field: that the time is TZ-aware
if not is_aware(frozen_time):
self.fail("frozen_time was a naive datetime but it should not be")

max_ages_and_expected_expiries = (
(300, frozen_time + datetime.timedelta(seconds=300)),
(1250000, frozen_time + datetime.timedelta(seconds=1250000)),
(-1, None),
)

for max_age, expected_expiry in max_ages_and_expected_expiries:
with self.subTest(max_age=max_age, expected_expiry=expected_expiry):
with patch.object(wagtaildraftsharing.models, "max_age", max_age):
revision = self.create_revision()

link = WagtaildraftsharingLink.objects.get_or_create_for_revision(
revision=revision,
user=self.test_user,
)

assert link.active_until == expected_expiry, (
link.active_until,
expected_expiry,
)


class TestWagtaildraftsharingLinkModel(TestCase):
Expand Down
35 changes: 0 additions & 35 deletions wagtaildraftsharing/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import datetime
import json
from unittest.mock import patch

from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.http import Http404
from django.test import RequestFactory, TestCase
from django.urls import reverse
from django.utils.timezone import is_aware
from django.utils.timezone import now as timezone_now
from freezegun import freeze_time
from wagtail_factories import PageFactory

import wagtaildraftsharing.views
from wagtaildraftsharing.models import WagtaildraftsharingLink
from wagtaildraftsharing.views import CreateSharingLinkView, SharingLinkView

Expand Down Expand Up @@ -107,37 +103,6 @@ def test_create_sharing_link_view__anonymous_user_not_allowed(self):
)
self.assertEqual(WagtaildraftsharingLink.objects.count(), 0)

@freeze_time(FROZEN_TIME_ISOFORMATTED)
def test_create_sharing_link_view__max_age_from_settings(self):
frozen_time = datetime.datetime.fromisoformat(FROZEN_TIME_ISOFORMATTED)

# Ensure we've got a level playing field: that the time is TZ-aware
if not is_aware(frozen_time):
self.fail("frozen_time was a naive datetime but it should not be")

max_ages_and_expected_expiries = (
(300, frozen_time + datetime.timedelta(seconds=300)),
(1250000, frozen_time + datetime.timedelta(seconds=1250000)),
(-1, None),
)

for max_age, expected_expiry in max_ages_and_expected_expiries:
with self.subTest(max_age=max_age, expected_expiry=expected_expiry):
with patch.object(wagtaildraftsharing.views, "max_age", max_age):
revision = self.create_revision()
request = self.factory.post("/create/", {"revision": revision.id})
request.user = self.superuser

response = CreateSharingLinkView.as_view()(request)
self.assertEqual(response.status_code, 200)

link = WagtaildraftsharingLink.objects.last()

assert link.active_until == expected_expiry, (
link.active_until,
expected_expiry,
)


class SharingLinkViewTests(TestCase):
@classmethod
Expand Down
13 changes: 13 additions & 0 deletions wagtaildraftsharing/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from datetime import timezone

from django.utils.timezone import is_aware, make_aware
from django.utils.timezone import now as timezone_now


def tz_aware_utc_now():
now = timezone_now()
# Depending on your version of Django and/or setting.TZ_NOW, timezone_now()
# may not actually be TZ aware, but we always want it to be for these links
if not is_aware(now):
now = make_aware(now, timezone.utc)
return now
47 changes: 5 additions & 42 deletions wagtaildraftsharing/views.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,20 @@
import uuid
from datetime import timezone

from django.http import Http404, JsonResponse
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.utils.timezone import is_aware, make_aware, timedelta
from django.utils.timezone import now as timezone_now
from django.views.generic import CreateView
from wagtail.admin.auth import user_has_any_page_permission, user_passes_test
from wagtail.admin.views.generic.preview import PreviewRevision
from wagtail.log_actions import log
from wagtail.models import Page, Revision

from wagtaildraftsharing.actions import WAGTAILDRAFTSHARING_CREATE_SHARING_LINK
from wagtaildraftsharing.forms import CreateWagtaildraftsharingLinkForm
from wagtaildraftsharing.models import WagtaildraftsharingLink

from . import settings as draftsharing_settings

max_age = draftsharing_settings.WAGTAILDRAFTSHARING_MAX_AGE


def _tz_aware_utc_now():
now = timezone_now()
# Depending on your version of Django and/or setting.TZ_NOW, timezone_now()
# may not actually be TZ aware, but we always want it to be for these links
if not is_aware(now):
now = make_aware(now, timezone.utc)
return now
from wagtaildraftsharing.utils import tz_aware_utc_now


class SharingLinkView(PreviewRevision):
def setup(self, request, *args, **kwargs):
key = kwargs.pop("key")
now = _tz_aware_utc_now()
now = tz_aware_utc_now()

sharing_link = get_object_or_404(
WagtaildraftsharingLink,
Expand All @@ -59,28 +40,10 @@ class CreateSharingLinkView(CreateView):
form_class = CreateWagtaildraftsharingLinkForm

def form_valid(self, form):
revision = form.cleaned_data["revision"]
key = uuid.uuid4()
if max_age > 0:
active_until = _tz_aware_utc_now() + timedelta(seconds=max_age)
else:
active_until = None
sharing_link, created = WagtaildraftsharingLink.objects.get_or_create(
revision=revision,
defaults={
"key": key,
"created_by": self.request.user,
"active_until": active_until,
},
sharing_link = WagtaildraftsharingLink.objects.get_or_create_for_revision(
revision=form.cleaned_data["revision"],
user=self.request.user,
)
if created:
log(
instance=revision.content_object,
action=WAGTAILDRAFTSHARING_CREATE_SHARING_LINK,
user=self.request.user,
revision=revision,
data={"revision": revision.id},
)
return JsonResponse({"url": sharing_link.url})

def form_invalid(self, form):
Expand Down

0 comments on commit d829af8

Please sign in to comment.