Skip to content

Commit 646823b

Browse files
Make volumes default to NULL
Closes #30
1 parent c64189b commit 646823b

File tree

16 files changed

+175
-82
lines changed

16 files changed

+175
-82
lines changed

.github/workflows/tests.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ jobs:
3030
- name: "Set up coveralls"
3131
uses: AndreMiras/coveralls-python-action@v20201129
3232
if: success()
33+
continue-on-error: true
3334
with:
3435
parallel: true
3536
flag-name: SQLite
@@ -79,6 +80,7 @@ jobs:
7980
- name: "Set up coveralls"
8081
uses: AndreMiras/coveralls-python-action@v20201129
8182
if: success()
83+
continue-on-error: true
8284
with:
8385
parallel: true
8486
flag-name: MySQL
@@ -129,6 +131,7 @@ jobs:
129131
- name: "Set up coveralls"
130132
uses: AndreMiras/coveralls-python-action@v20201129
131133
if: success()
134+
continue-on-error: true
132135
with:
133136
parallel: true
134137
flag-name: PostgreSQL

MangAdventure/templates/index.html

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@ <h2 class="release-title">
1717
</a>
1818
</h2>
1919
<div class="release-info">
20-
{% with vol=release.volume num=release.number %}
21-
<a href="{{ release.get_absolute_url }}" title="{{ release.title }}"
22-
class="release-link{% if release.final %} end{% endif %}">{{ release }}</a>
23-
{% endwith %}
20+
<a href="{{ release.get_absolute_url }}" title="{{ release.title }}"
21+
class="release-link{% if release.final %} end{% endif %}">{{ release }}</a>
2422
<div class="chapter-metadata">
2523
<span class="divider"></span>
2624
{% with groups=release.groups.all %}

api/v1/views.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,11 @@ def _latest(request: HttpRequest, slug: Optional[str] = None,
4242
).distinct().get().modified
4343
if num is None:
4444
return Chapter.objects.only('modified').filter(
45-
series__slug=slug, volume=vol, published__lte=tz.now()
45+
series__slug=slug, volume=vol or None,
46+
published__lte=tz.now()
4647
).latest().modified
4748
return Chapter.objects.only('modified').filter(
48-
series__slug=slug, volume=vol,
49+
series__slug=slug, volume=vol or None,
4950
number=num, published__lte=tz.now()
5051
).latest().modified
5152
except ObjectDoesNotExist:
@@ -256,7 +257,9 @@ def volume(request: HttpRequest, slug: str, vol: int) -> JsonResponse:
256257
.prefetch_related('chapters__pages').get(slug=slug)
257258
except ObjectDoesNotExist:
258259
return JsonError('Not found', 404)
259-
chapters = _series.chapters.filter(volume=vol, published__lte=tz.now())
260+
chapters = _series.chapters.filter(
261+
volume=vol or None, published__lte=tz.now()
262+
)
260263
if not chapters:
261264
return JsonError('Not found', 404)
262265
return JsonResponse(_volume_response(request, chapters))
@@ -280,7 +283,7 @@ def chapter(request: HttpRequest, slug: str,
280283
"""
281284
try:
282285
_chapter = Chapter.objects.prefetch_related('pages').get(
283-
series__slug=slug, volume=vol,
286+
series__slug=slug, volume=vol or None,
284287
number=num, published__lte=tz.now()
285288
)
286289
except ObjectDoesNotExist:

config/management/commands/fs2import.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def handle(self, *args: str, **options: str):
135135
chapter = Chapter(
136136
id=cid, series_id=sid,
137137
title=self._get_column(c, 'name'),
138-
volume=volume, number=number
138+
volume=volume or None, number=number
139139
)
140140
self._print(
141141
f'- Found {self._sql_name("Chapter")}: {chapter.series} '

docs/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ v0.8.4
66

77
* Fixed licensed chapter page
88
* Fixed invalid chapter page
9+
* Fixed API showing licensed chapters
10+
* Made chapter volume default to ``NULL``
911

1012
v0.8.3
1113
^^^^^^

reader/api.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
from typing import TYPE_CHECKING, Type
66
from warnings import filterwarnings
77

8-
from django.db.models import Count, Max, Prefetch, Q, Sum
8+
from django.db.models import Count, F, Max, Prefetch, Q, Sum
99
from django.utils import timezone as tz
1010

11+
from rest_framework.exceptions import APIException
1112
from rest_framework.mixins import (
1213
CreateModelMixin, DestroyModelMixin, ListModelMixin,
1314
RetrieveModelMixin, UpdateModelMixin
1415
)
1516
from rest_framework.parsers import MultiPartParser
17+
from rest_framework.response import Response
1618
from rest_framework.viewsets import GenericViewSet, ModelViewSet
1719

1820
from api.v2.mixins import CORSMixin
@@ -23,12 +25,19 @@
2325
from . import filters, models, serializers
2426

2527
if TYPE_CHECKING: # pragma: no cover
26-
from django.db.models.query import QuerySet
28+
from django.db.models.query import QuerySet # isort:skip
29+
from rest_framework.request import Request # isort:skip
2730

2831
# XXX: We are overriding the "Series" schema on purpose.
2932
filterwarnings('ignore', '^Schema', module=OpenAPISchema.__base__.__module__)
3033

3134

35+
class _LegalException(APIException):
36+
status_code = 451
37+
default_detail = 'This series is licensed.'
38+
default_code = 'licensed_series'
39+
40+
3241
class ArtistViewSet(CORSMixin, ModelViewSet):
3342
"""
3443
API endpoints for artists.
@@ -145,6 +154,13 @@ class SeriesViewSet(CORSMixin, ModelViewSet):
145154
ordering = ('title',)
146155
lookup_field = 'slug'
147156

157+
def retrieve(self, request: Request, *args, **kwargs) -> Response:
158+
instance = self.get_object()
159+
if instance.licensed:
160+
raise _LegalException()
161+
serializer = self.get_serializer(instance)
162+
return Response(serializer.data)
163+
148164
def get_queryset(self) -> QuerySet:
149165
q = Q(chapters__published__lte=tz.now())
150166
return models.Series.objects.annotate(
@@ -167,18 +183,24 @@ class CubariViewSet(RetrieveModelMixin, CORSMixin, GenericViewSet):
167183
serializer_class = serializers.CubariSerializer
168184
lookup_field = 'slug'
169185

186+
def retrieve(self, request: Request, *args, **kwargs) -> Response:
187+
instance = self.get_object()
188+
if instance.licensed:
189+
raise _LegalException()
190+
serializer = self.get_serializer(instance)
191+
return Response(serializer.data)
192+
170193
def get_queryset(self) -> QuerySet:
171194
pages = models.Page.objects.order_by('number')
172195
groups = Group.objects.only('name')
173196
chapters = models.Chapter.objects.prefetch_related(
174197
Prefetch('pages', queryset=pages),
175198
Prefetch('groups', queryset=groups)
176-
).order_by('volume', 'number').only(
199+
).order_by(F('volume').asc(nulls_last=True), 'number').only(
177200
'title', 'number', 'volume', 'modified', 'series'
178201
)
179202
return models.Series.objects.defer(
180-
'manager_id', 'modified',
181-
'created', 'completed', 'licensed'
203+
'manager_id', 'modified', 'created', 'completed'
182204
).prefetch_related(
183205
Prefetch('authors'), Prefetch('artists'),
184206
Prefetch('chapters', queryset=chapters)

reader/feeds.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from django.conf import settings
99
from django.contrib.syndication.views import Feed
10-
from django.db.models import Prefetch
10+
from django.db.models import F, Prefetch
1111
from django.utils import timezone as tz
1212
from django.utils.feedgenerator import Atom1Feed
1313

@@ -147,7 +147,7 @@ def get_object(self, request: HttpRequest, slug:
147147
).filter(
148148
published__lte=tz.now(),
149149
series__licensed=False
150-
)
150+
).order_by(F('volume').asc(nulls_last=True), 'number')
151151
return Series.objects.only(
152152
'slug', 'title', 'licensed', 'format'
153153
).prefetch_related(

reader/filters.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,8 @@ def filter_queryset(self, request: Request, queryset: QuerySet,
262262
'error': f'{params} are required parameters.'
263263
})
264264
series = request.query_params['series']
265-
volume = request.query_params['volume']
266-
number = request.query_params['number']
265+
volume = int(request.query_params['volume']) or None
266+
number = float(request.query_params['number'])
267267
if request.query_params.get('track') == 'true':
268268
Chapter.track_view(
269269
series__slug=series,

reader/migrations/0001_squashed.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from MangAdventure import storage, validators
77

8-
from reader.models import _cover_uploader, _PageNumberField
8+
from reader.models import _cover_uploader, _NonZeroIntegerField
99

1010

1111
class Migration(migrations.Migration):
@@ -139,7 +139,7 @@ class Migration(migrations.Migration):
139139
storage=storage.CDNStorage(),
140140
upload_to='', max_length=255
141141
)),
142-
('number', _PageNumberField()),
142+
('number', _NonZeroIntegerField()),
143143
('chapter', models.ForeignKey(
144144
on_delete=models.deletion.CASCADE,
145145
related_name='pages', to='reader.Chapter'
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from django.core.validators import MinValueValidator
2+
from django.db import migrations, models
3+
4+
from reader.models import _NonZeroIntegerField
5+
6+
7+
class Migration(migrations.Migration):
8+
dependencies = [('reader', '0009_constraints')]
9+
10+
operations = [
11+
migrations.AlterModelOptions(
12+
name='chapter',
13+
options={
14+
'get_latest_by': ('published', 'modified')
15+
}
16+
),
17+
migrations.AlterField(
18+
model_name='chapter',
19+
name='number',
20+
field=models.FloatField(
21+
help_text='The number of the chapter.',
22+
default=0, validators=(MinValueValidator(0),)
23+
)
24+
),
25+
migrations.AlterField(
26+
model_name='chapter',
27+
name='volume',
28+
field=_NonZeroIntegerField(
29+
null=True, blank=True, help_text=(
30+
'The volume of the chapter. '
31+
'Leave blank if the series has no volumes.'
32+
)
33+
)
34+
),
35+
migrations.RunSQL(
36+
sql='UPDATE reader_chapter SET volume = NULL WHERE volume = 0;',
37+
reverse_sql=(
38+
'UPDATE reader_chapter SET volume = 0 WHERE volume IS NULL;'
39+
)
40+
),
41+
migrations.AddConstraint(
42+
model_name='chapter',
43+
constraint=models.CheckConstraint(
44+
name='volume_number_positive',
45+
check=models.Q(
46+
('volume__isnull', True),
47+
('volume__gt', 0), _connector='OR'
48+
)
49+
)
50+
)
51+
]

0 commit comments

Comments
 (0)