Allow sharing drafts publicly with a draft key
This commit is contained in:
parent
6b52f6b2b2
commit
d1eced4f1f
6 changed files with 70 additions and 4 deletions
|
@ -36,6 +36,7 @@ class ArticleAdmin(admin.ModelAdmin):
|
|||
("created_at", "updated_at"),
|
||||
"views_count",
|
||||
"has_code",
|
||||
"draft_public_url",
|
||||
]
|
||||
},
|
||||
),
|
||||
|
@ -57,6 +58,7 @@ class ArticleAdmin(admin.ModelAdmin):
|
|||
"views_count",
|
||||
"status",
|
||||
"published_at",
|
||||
"draft_public_url",
|
||||
]
|
||||
prepopulated_fields = {"slug": ("title",)}
|
||||
change_form_template = "articles/article_change_form.html"
|
||||
|
@ -80,7 +82,17 @@ class ArticleAdmin(admin.ModelAdmin):
|
|||
messages.success(request, f"{len(queryset)} articles unpublished.")
|
||||
|
||||
unpublish.short_description = "Unpublish selected articles"
|
||||
actions = [publish, unpublish]
|
||||
|
||||
def refresh_draft_key(self, request, queryset):
|
||||
if not request.user.has_perm("articles.change_article"):
|
||||
messages.warning(request, "You're not allowed to do this.")
|
||||
return
|
||||
for article in queryset:
|
||||
article.refresh_draft_key()
|
||||
messages.success(request, f"{len(queryset)} draft keys refreshed.")
|
||||
|
||||
refresh_draft_key.short_description = "Refresh draft key of selected articles"
|
||||
actions = [publish, unpublish, refresh_draft_key]
|
||||
|
||||
class Media:
|
||||
css = {"all": ("admin_articles.css",)}
|
||||
|
|
20
articles/migrations/0026_article_draft_key.py
Normal file
20
articles/migrations/0026_article_draft_key.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 3.1.4 on 2020-12-27 18:43
|
||||
|
||||
import uuid
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("articles", "0025_article_custom_css"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="article",
|
||||
name="draft_key",
|
||||
field=models.UUIDField(default=uuid.uuid4),
|
||||
),
|
||||
]
|
|
@ -1,4 +1,5 @@
|
|||
import re
|
||||
import uuid
|
||||
from functools import cached_property
|
||||
|
||||
import html2text
|
||||
|
@ -12,6 +13,7 @@ from django.utils import timezone
|
|||
from markdown.extensions.codehilite import CodeHiliteExtension
|
||||
|
||||
from articles.markdown import LazyLoadingImageExtension
|
||||
from articles.utils import build_full_absolute_url
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
|
@ -47,6 +49,7 @@ class Article(AdminUrlMixin, models.Model):
|
|||
has_code = models.BooleanField(default=False, blank=True)
|
||||
is_home = models.BooleanField(default=False, blank=True)
|
||||
custom_css = models.TextField(blank=True)
|
||||
draft_key = models.UUIDField(default=uuid.uuid4)
|
||||
|
||||
class Meta:
|
||||
ordering = ["-published_at"]
|
||||
|
@ -108,3 +111,12 @@ class Article(AdminUrlMixin, models.Model):
|
|||
if not self.slug:
|
||||
self.slug = slugify(self.title)
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def draft_public_url(self):
|
||||
url = self.get_absolute_url() + f"?draft_key={self.draft_key}"
|
||||
return build_full_absolute_url(request=None, url=url)
|
||||
|
||||
def refresh_draft_key(self):
|
||||
self.draft_key = uuid.uuid4()
|
||||
self.save()
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import uuid
|
||||
|
||||
import pytest
|
||||
from django.core.management import call_command
|
||||
from django.utils import timezone
|
||||
|
@ -40,6 +42,7 @@ def unpublished_article(author: User) -> Article:
|
|||
published_at=None,
|
||||
slug="some-draft-article-slug",
|
||||
content="## some draft article markdown\n\n[a draft article link](https://article.com)",
|
||||
draft_key=uuid.uuid4(),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -41,6 +41,10 @@ def test_access_article_by_slug(client: Client, published_article: Article):
|
|||
|
||||
def _test_access_article_by_slug(client: Client, item: Article):
|
||||
res = client.get(reverse("article-detail", kwargs={"slug": item.slug}))
|
||||
_assert_article_is_rendered(item, res)
|
||||
|
||||
|
||||
def _assert_article_is_rendered(item: Article, res):
|
||||
assert res.status_code == 200
|
||||
content = res.content.decode("utf-8")
|
||||
assert item.title in content
|
||||
|
@ -57,6 +61,17 @@ def test_anonymous_cant_access_draft_detail(
|
|||
assert res.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_anonymous_can_access_draft_detail_with_key(
|
||||
client: Client, unpublished_article: Article
|
||||
):
|
||||
res = client.get(
|
||||
reverse("article-detail", kwargs={"slug": unpublished_article.slug})
|
||||
+ f"?draft_key={unpublished_article.draft_key}"
|
||||
)
|
||||
_assert_article_is_rendered(unpublished_article, res)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_user_can_access_draft_detail(
|
||||
client: Client, author: User, unpublished_article: Article
|
||||
|
|
|
@ -48,10 +48,14 @@ class ArticleDetailView(generic.DetailView):
|
|||
template_name = "articles/article_detail.html"
|
||||
|
||||
def get_queryset(self):
|
||||
key = self.request.GET.get("draft_key")
|
||||
if key:
|
||||
return Article.objects.filter(draft_key=key)
|
||||
|
||||
queryset = super().get_queryset()
|
||||
if self.request.user.is_authenticated:
|
||||
return queryset
|
||||
return queryset.filter(status=Article.PUBLISHED)
|
||||
if not self.request.user.is_authenticated:
|
||||
queryset = queryset.filter(status=Article.PUBLISHED)
|
||||
return queryset
|
||||
|
||||
def get_object(self, queryset=None) -> Article:
|
||||
obj = super().get_object(queryset) # type: Article
|
||||
|
|
Reference in a new issue