Skip to content

Commit 9d2c1f6

Browse files
committed
refactor: quiblet serializer and add related quibs
1 parent ad9f4d0 commit 9d2c1f6

File tree

7 files changed

+184
-14
lines changed

7 files changed

+184
-14
lines changed

backend/apps/quib/api/v1/serializers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,9 @@ class QuibSlimSerializer(serializers.ModelSerializer):
1919
class Meta:
2020
model = Quib
2121
exclude = ('quibber',)
22+
23+
def get_cover(self, obj):
24+
request = self.context.get('request')
25+
if obj.cover:
26+
return request.build_absolute_uri(obj.cover) if request else obj.cover
27+
return None

backend/apps/quiblet/api/v1/serializers.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,36 @@
1-
from rest_framework import exceptions, serializers
1+
from rest_framework import serializers
2+
3+
from apps.user.models import Profile
24

35
from ...models import Quiblet
46

57

6-
class QuibletSerializer(serializers.ModelSerializer):
8+
class RangerSerializer(serializers.ModelSerializer):
9+
name = serializers.SerializerMethodField()
10+
11+
class Meta:
12+
model = Profile
13+
fields = ('username', 'avatar', 'name')
14+
15+
def get_name(self, obj):
16+
if obj.first_name or obj.last_name:
17+
truthy_fields = filter(None, [obj.first_name, obj.last_name])
18+
return " ".join(truthy_fields)
19+
return None
20+
21+
22+
class QuibletDetailSerializer(serializers.ModelSerializer):
23+
rangers = RangerSerializer(many=True)
24+
725
class Meta:
826
model = Quiblet
927
fields = '__all__'
1028

11-
def validate_name(self, name):
12-
if Quiblet.objects.filter(name__iexact=name).exists():
13-
raise exceptions.ValidationError(
14-
f"Quiblet with name {name} already exists (case-insensitive)."
15-
)
1629

17-
return name
30+
class QuibletSerializer(serializers.ModelSerializer):
31+
class Meta:
32+
model = Quiblet
33+
fields = '__all__'
1834

1935

2036
class QuibletSlimSerializer(serializers.ModelSerializer):

backend/apps/quiblet/api/v1/viewsets.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,34 @@
22
from typing import cast
33

44
from django.db.models import QuerySet
5+
from drf_spectacular.utils import extend_schema
56
from rest_framework import exceptions, response, viewsets
67
from rest_framework.decorators import action
78

9+
from apps.quib.api.v1.serializers import QuibSlimSerializer
810
from common.patches.request import PatchedHttpRequest
911

1012
from ...models import Quiblet
11-
from .serializers import QuibletExistsSerializer, QuibletSerializer
13+
from .serializers import (
14+
QuibletDetailSerializer,
15+
QuibletExistsSerializer,
16+
QuibletSerializer,
17+
)
1218

1319

1420
class QuibletViewSet(viewsets.ModelViewSet):
1521
queryset = Quiblet.objects.all()
22+
# default serializer
1623
serializer_class = QuibletSerializer
1724
lookup_field = 'name'
1825

26+
# extra custom serializers
27+
serializer_classes = {
28+
'exists': QuibletExistsSerializer,
29+
'quibs': QuibSlimSerializer,
30+
'retrieve': QuibletDetailSerializer,
31+
}
32+
1933
def get_queryset(self) -> QuerySet[Quiblet]: # pyright: ignore
2034
return super().get_queryset()
2135

@@ -31,8 +45,8 @@ def get_object(self) -> Quiblet: # pyright: ignore
3145
return obj
3246

3347
def get_serializer_class(self): # pyright: ignore
34-
if self.action == 'exists':
35-
return QuibletExistsSerializer
48+
if self.action in self.serializer_classes:
49+
return self.serializer_classes[self.action]
3650
return super().get_serializer_class()
3751

3852
@action(detail=True, methods=[HTTPMethod.GET])
@@ -46,6 +60,14 @@ def exists(self, request, name=None):
4660

4761
return response.Response(res)
4862

63+
@extend_schema(responses=QuibSlimSerializer(many=True))
64+
@action(detail=True, methods=[HTTPMethod.GET])
65+
def quibs(self, request, name=None):
66+
quibs = self.get_object().quibs.all() # pyright: ignore
67+
serializer = QuibSlimSerializer(quibs, many=True, context={'request': request})
68+
69+
return response.Response(serializer.data)
70+
4971
def perform_create(self, serializer):
5072
patched_request = cast(PatchedHttpRequest, self.request)
5173

frontend/src/lib/clients/v1.ts

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,22 @@ export interface paths {
8484
patch?: never;
8585
trace?: never;
8686
};
87+
'/api/v1/quiblets/{name}/quibs/': {
88+
parameters: {
89+
query?: never;
90+
header?: never;
91+
path?: never;
92+
cookie?: never;
93+
};
94+
get: operations['quiblets_quibs_list'];
95+
put?: never;
96+
post?: never;
97+
delete?: never;
98+
options?: never;
99+
head?: never;
100+
patch?: never;
101+
trace?: never;
102+
};
87103
'/api/v1/quibs/': {
88104
parameters: {
89105
query?: never;
@@ -772,6 +788,24 @@ export interface components {
772788
members?: number[];
773789
rangers?: number[];
774790
};
791+
QuibletDetail: {
792+
readonly id: number;
793+
rangers: components['schemas']['Ranger'][];
794+
/**
795+
* Create at
796+
* Format: date-time
797+
*/
798+
readonly created_at: string;
799+
/** Format: uri */
800+
avatar?: string | null;
801+
is_public?: boolean;
802+
name: string;
803+
description: string;
804+
title?: string | null;
805+
/** Format: uri */
806+
banner?: string | null;
807+
members?: number[];
808+
};
775809
QuibletExists: {
776810
exists: boolean;
777811
name: string;
@@ -1960,6 +1994,13 @@ export interface components {
19601994
type: components['schemas']['ValidationErrorEnum'];
19611995
errors: components['schemas']['QuibsUpdateError'][];
19621996
};
1997+
Ranger: {
1998+
/** @description Required. 25 characters or fewer. Letters, digits and ./_ only. */
1999+
username: string;
2000+
/** Format: uri */
2001+
avatar?: string | null;
2002+
readonly name: string;
2003+
};
19632004
/**
19642005
* @description * `server_error` - Server Error
19652006
* @enum {string}
@@ -2780,7 +2821,7 @@ export interface operations {
27802821
[name: string]: unknown;
27812822
};
27822823
content: {
2783-
'application/json': components['schemas']['Quiblet'];
2824+
'application/json': components['schemas']['QuibletDetail'];
27842825
};
27852826
};
27862827
404: {
@@ -2976,6 +3017,43 @@ export interface operations {
29763017
};
29773018
};
29783019
};
3020+
quiblets_quibs_list: {
3021+
parameters: {
3022+
query?: never;
3023+
header?: never;
3024+
path: {
3025+
name: string;
3026+
};
3027+
cookie?: never;
3028+
};
3029+
requestBody?: never;
3030+
responses: {
3031+
200: {
3032+
headers: {
3033+
[name: string]: unknown;
3034+
};
3035+
content: {
3036+
'application/json': components['schemas']['QuibSlim'][];
3037+
};
3038+
};
3039+
404: {
3040+
headers: {
3041+
[name: string]: unknown;
3042+
};
3043+
content: {
3044+
'application/json': components['schemas']['ErrorResponse404'];
3045+
};
3046+
};
3047+
500: {
3048+
headers: {
3049+
[name: string]: unknown;
3050+
};
3051+
content: {
3052+
'application/json': components['schemas']['ErrorResponse500'];
3053+
};
3054+
};
3055+
};
3056+
};
29793057
quibs_list: {
29803058
parameters: {
29813059
query?: never;

frontend/src/routes/(app)/q/[name]/+layout.svelte

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import type { PageData } from './$types';
44
import { FormatDate } from '$lib/functions/date';
55
import { pluralize } from '$lib/functions/pluralize';
6+
import Avatar from '$lib/components/ui/avatar.svelte';
67
78
const { data, children }: { data: PageData; children: Snippet } = $props();
89
const { quiblet } = data;
@@ -35,5 +36,27 @@
3536
>
3637
</div>
3738
</div>
39+
<div class="divider my-0 before:h-px after:h-px"></div>
40+
<div class="flex items-center gap-2">
41+
<h3 class="text-sm font-medium">Rangers</h3>
42+
<div class="tooltip tooltip-right flex" data-tip="Moderators">
43+
<coreicons-shape-help-circle class="size-[0.85rem]"></coreicons-shape-help-circle>
44+
</div>
45+
</div>
46+
{#if quiblet?.rangers}
47+
<div class="flex flex-col gap-2">
48+
{#each quiblet?.rangers as ranger}
49+
<div class="flex items-center gap-2">
50+
<Avatar src={ranger.avatar} />
51+
<div class="flex flex-col">
52+
<a href="/u/{ranger.username}" class="text-sm font-medium"
53+
>u/{ranger.username}</a
54+
>
55+
<span class="text-xs text-base-content/75">{ranger.name}</span>
56+
</div>
57+
</div>
58+
{/each}
59+
</div>
60+
{/if}
3861
</div>
3962
</div>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import client from '$lib/clients/client';
2+
import type { PageServerLoad } from './$types';
3+
4+
export const load: PageServerLoad = async ({ params }) => {
5+
const { data, error, response } = await client.GET('/api/v1/quiblets/{name}/quibs/', {
6+
params: {
7+
path: { name: params.name }
8+
}
9+
});
10+
11+
if (response.ok && data) {
12+
return { quibs: data };
13+
} else if (error) {
14+
console.error(error);
15+
}
16+
};

frontend/src/routes/(app)/q/[name]/+page.svelte

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
<script lang="ts">
2+
import Quib from '$lib/components/pages/home/quib.svelte';
3+
import QuibsHeader from '$lib/components/pages/home/quibs_header.svelte';
24
import Avatar from '$lib/components/ui/avatar.svelte';
35
import { cn } from '$lib/functions/classnames';
46
import type { PageData } from './$types';
57
68
const { data }: { data: PageData } = $props();
7-
const { quiblet } = data;
9+
const { quiblet, quibs } = data;
810
</script>
911

1012
<svelte:head>
@@ -22,7 +24,7 @@
2224
<div class="absolute inset-x-0 -bottom-12 flex items-end justify-between px-4">
2325
<div class="flex items-end gap-2">
2426
<Avatar class="!size-20 outline outline-8 outline-base-300" src={quiblet?.avatar} />
25-
<h3 class="text-3xl font-bold text-info">q/{quiblet?.name}</h3>
27+
<h3 class="text-2xl font-bold text-info">q/{quiblet?.name}</h3>
2628
</div>
2729
<div class="flex items-center gap-2">
2830
<button class="btn btn-primary h-10 px-3" aria-label="Create a Post">
@@ -38,3 +40,10 @@
3840
</div>
3941
</div>
4042
</div>
43+
<div class="h-12"></div>
44+
<QuibsHeader />
45+
{#if quibs}
46+
{#each quibs as quib}
47+
<Quib {...quib} />
48+
{/each}
49+
{/if}

0 commit comments

Comments
 (0)