Skip to content

Commit

Permalink
Members-only content confactor (ZcashFoundation#142)
Browse files Browse the repository at this point in the history
Co-authored-by: Jonathan Bird <[email protected]>
  • Loading branch information
skyl and birdify authored May 9, 2023
1 parent 7ec2ed2 commit 91b02b5
Show file tree
Hide file tree
Showing 17 changed files with 228 additions and 38 deletions.
1 change: 1 addition & 0 deletions py/dj/apps/g12f/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class PageAdmin(admin.ModelAdmin):
# above is free
'is_verified',
'is_published',
'is_subscriber_only',
'vanity',
'p2paddr',
'p2pqr',
Expand Down
18 changes: 18 additions & 0 deletions py/dj/apps/g12f/migrations/0068_add_subscriber_only.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2023-04-22 01:42

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('g12f', '0067_zpage_publish_at'),
]

operations = [
migrations.AddField(
model_name='zpage',
name='is_subscriber_only',
field=models.BooleanField(default=False),
),
]
1 change: 1 addition & 0 deletions py/dj/apps/g12f/models/zpage.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class zPage(models.Model):
# TODO: remove completely
is_funded = models.BooleanField(default=False)
is_published = models.BooleanField(default=False)
is_subscriber_only = models.BooleanField(default=False)

is_verified = models.BooleanField(default=False)
f2z_score = models.DecimalField(
Expand Down
32 changes: 24 additions & 8 deletions py/dj/apps/g12f/serializers/zpage.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Meta:
'title',
'content',
'is_published',
'is_subscriber_only',
'category',
'f2z_score',
'get_url',
Expand All @@ -50,7 +51,8 @@ def __init__(self, *args, **kwargs):
if hasattr(self, 'context') and 'request' in self.context:
request = self.context['request']
self.fields['featured_image'].queryset = GenericFile.objects.filter(
owner=request.user)
owner=request.user
)

def validate_featured_image(self, value):
if value:
Expand Down Expand Up @@ -130,6 +132,7 @@ class Meta:
# 'content',
'category',
'is_published',
'is_subscriber_only',
'featured_image',
# #####################
'is_verified',
Expand All @@ -143,14 +146,21 @@ class Meta:
'tags',
)
read_only_fields = [
'free2zaddr', 'is_verified',
'total', 'f2z_score', 'updated_at', 'created_at',
'get_url', 'creator', 'featured_image',
'free2zaddr',
'is_verified',
'total',
'f2z_score',
'updated_at',
'created_at',
'get_url',
'creator',
'featured_image',
]

def get_content(self, instance):
import markdown2
from django.utils.html import strip_tags

html = markdown2.markdown(instance.content)
text = strip_tags(html)
return f"{text[:365]}..."
Expand Down Expand Up @@ -186,6 +196,7 @@ class Meta:
'content',
'category',
'is_published',
'is_subscriber_only',
'total_raised',
'featured_image',
# #####################
Expand All @@ -201,8 +212,13 @@ class Meta:
'publish_at',
)
read_only_fields = [
'free2zaddr', 'is_verified'
'total', 'f2z_score', 'comments',
'created_at', 'updated_at', 'creator',
'get_url', 'featured_image',
'free2zaddr',
'is_verified' 'total',
'f2z_score',
'comments',
'created_at',
'updated_at',
'creator',
'get_url',
'featured_image',
]
33 changes: 33 additions & 0 deletions py/dj/apps/g12f/views/exception_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from rest_framework.views import exception_handler
from rest_framework.exceptions import APIException
from ..serializers import zPageDetailSerializer

# custom exception handler can be used to trigger a modal on a zPage


class RedirectWithModalException(APIException):
status_code = 200
default_detail = "This is a custom redirect exception with modal flag."
default_code = "redirect_with_modal"

def __init__(self, page, show_modal=True):
self.show_modal = show_modal
self.page = page
super().__init__(self.default_detail, self.default_code)


def custom_exception_handler(exc, context):
response = exception_handler(exc, context)

if isinstance(exc, RedirectWithModalException):
# Swap out the content so that an intrepid person can't
# pull it out of the response
exc.page.content = "Subscribe for exclusive content"
page_serializer = zPageDetailSerializer(instance=exc.page)

response.data = {
'show_modal': exc.show_modal,
'page': page_serializer.data
}

return response
41 changes: 36 additions & 5 deletions py/dj/apps/g12f/views/zpage.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import uuid
from django import http
from django.db.utils import IntegrityError

from django.shortcuts import get_object_or_404, render
from django.utils import timezone

from dj.apps.g12f.models.creator import Subscription
from rest_framework import (
generics, viewsets, filters, exceptions, permissions
)
from rest_framework import status
from django.db.models import Q

from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from .exception_handler import RedirectWithModalException


from dj.apps.g12f.serializers import (
zPageListSerializer, zPageUpdateSerializer,
Expand Down Expand Up @@ -102,10 +105,20 @@ def get_object(self):
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
pk = self.kwargs.get('pk')
qpk = Q(free2zaddr=pk) | Q(vanity__iexact=pk)
try:
return queryset.filter(Q(free2zaddr=pk) | Q(vanity__iexact=pk)).get()
return queryset.filter(qpk).get()
# TODO: could be made abstracter
except zPage.DoesNotExist:
qs = zPage.objects.filter(
is_subscriber_only=True,
is_published=True,
).filter(qpk)
if qs.count():
raise RedirectWithModalException(
page=qs.get(),
show_modal=True
)
raise http.Http404


Expand Down Expand Up @@ -189,6 +202,7 @@ def get_queryset(self):
has_auth = request.user.is_authenticated
is_owner = Q(creator=request.user)
published = Q(is_published=True)

if has_auth:
published = published | is_owner
in_search = published
Expand Down Expand Up @@ -272,8 +286,25 @@ def get_queryset(self):
)

if self.action == 'retrieve':
# TODO: members-only
return zPage.objects.filter(published)
user = request.user

if user.is_anonymous:
return zPage.objects.filter(
published & Q(is_subscriber_only=False))
else:
# TODO: this might be a little expensive
# to do on each retrieve for a page...
# get all the creators that user has subscribed to
star_ids = Subscription.objects.filter(
fan=user, expires__gte=timezone.now(),
).values_list(
"star_id", flat=True
)
return zPage.objects.filter(
Q(is_subscriber_only=False, is_published=True) |
Q(creator__id__in=star_ids, is_published=True) |
is_owner,
)

if self.action in ['update', 'partial_update', 'destroy']:
if request.user.is_anonymous:
Expand Down
7 changes: 7 additions & 0 deletions py/dj/apps/tuzis/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ def post(self, request, *args, **kwargs):
status=status.HTTP_400_BAD_REQUEST,
)

# should we set an automatic minimum member price to fix this bug?
if not star.member_price:
return Response(
data={'error': 'Creator did not set member price'},
status=status.HTTP_400_BAD_REQUEST,
)

# doesn't have enough tuzis
if fan.tuzis < star.member_price:
return Response(
Expand Down
1 change: 1 addition & 0 deletions py/dj/free2z/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
'rest_framework.authentication.BasicAuthentication',
'knox.auth.TokenAuthentication',
],
"EXCEPTION_HANDLER": "dj.apps.g12f.views.exception_handler.custom_exception_handler",
}

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
Expand Down
13 changes: 10 additions & 3 deletions ts/react/free2z/src/Begin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function Begin() {
const navigate = useNavigate()
const [_, setLoading] = useGlobalState("loading")
const [creator, setCreator] = useGlobalState("creator")
const [redirect, ___] = useGlobalState("redirect")
const [redirect, setRedirect] = useGlobalState("redirect")
const [loginModal, setLoginModal] = useGlobalState("loginModal")

useEffect(() => {
Expand All @@ -45,17 +45,24 @@ function Begin() {
axios
.get("/api/auth/user/")
.then((res) => {
// just to be paranoid
// Login to subscribe to zpage and then exit
// - don't get stuck.
let n = redirect
setRedirect("")

setCreator(res.data)
setLoginModal(false)
setLoading(false)
if (redirect) {
navigate(redirect)
if (n) {
navigate(n)
} else {
navigate("/profile")
}
})
.catch((res) => {
// setLoginModal(false)
// setRedirect("")
setLoading(false)
// setCreator({} as Creator)
})
Expand Down
19 changes: 19 additions & 0 deletions ts/react/free2z/src/EditPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export default function EditPage() {
// featured_image: null,
vanity: "",
is_published: false,
is_subscriber_only: false,
// category: "",
tags: [] as Tag[],
f2z_score: "0",
Expand Down Expand Up @@ -559,6 +560,24 @@ export default function EditPage() {
}}
/>

<FormControlLabel
control={
<Checkbox
// helperText="Subscriber only content"
value={page.is_subscriber_only}
checked={page.is_subscriber_only}
onClick={(ev: React.MouseEvent<HTMLButtonElement>) => {
setPage({
...page,
is_subscriber_only: !page.is_subscriber_only,
})
}}
/>
}
label="Subscriber only content"
/>


<F2ZDateTimePicker
value={page.publish_at || null}
setValue={(value) => {
Expand Down
Loading

0 comments on commit 91b02b5

Please sign in to comment.