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

Move the link generation out of the view, to make it more reusable from Python #4

Merged
merged 2 commits into from
Nov 13, 2024
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
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