From: Radek Czajka Date: Tue, 15 Apr 2025 13:52:18 +0000 (+0200) Subject: helper script X-Git-Url: https://git.mdrn.pl/wolnelektury.git/commitdiff_plain/refs/heads/master?ds=sidebyside;hp=1501fe79a5abe6fc214309ade4ceb85f6bfd0328 helper script --- diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..032024ba7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,57 @@ +FROM python:3.8 AS base + +ARG UID=1000 +ARG GID=1000 + +RUN apt-get update && apt-get install -y \ + git \ + calibre \ + texlive-xetex texlive-lang-polish \ + libespeak-dev + +COPY requirements/requirements.txt requirements.txt + +# numpy -> aeneas +RUN pip install numpy +RUN pip install aeneas + +RUN pip install --no-cache-dir -r requirements.txt +RUN pip install --no-cache-dir \ + psycopg2-binary \ + django-debug-toolbar==3.2.2 \ + python-bidi + +RUN addgroup --gid $GID app +RUN adduser --gid $GID --home /app --uid $UID app + +RUN apt-get install -y \ + texlive-extra-utils \ + texlive-lang-greek \ + texlive-lang-other \ + texlive-luatex \ + texlive-fonts-extra \ + texlive-fonts-extra-links \ + fonts-noto-core fonts-noto-extra + + +USER app + +# fonts +RUN cp -a /usr/local/lib/python*/site-packages/librarian/fonts /app/.fonts +RUN fc-cache + +WORKDIR /app/src + + +FROM base AS dev + +#RUN pip install --no-cache-dir coverage +USER app + + +FROM base AS prod + +RUN pip install --no-cache-dir gunicorn + +USER app +COPY src /app/src diff --git a/Makefile b/Makefile index d43ff1e6d..77d345df2 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,8 @@ -.PHONY: deploy test +.PHONY: deploy test shell + + +UID != id -u +GID != id -g deploy: src/wolnelektury/localsettings.py @@ -17,3 +21,11 @@ test: mv ../htmlcov.new ../htmlcov coverage report rm .coverage + + +shell: + UID=$(UID) GID=$(GID) docker-compose run --rm dev bash + + +build: + UID=$(UID) GID=$(GID) docker-compose build dev diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..89b2216d7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +services: + dev: + build: + context: . + target: dev + args: + - "UID=${UID}" + - "GID=${GID}" + volumes: + - ./src:/app/src + - ./var/media:/app/var/media + - ./var/static:/app/var/static + - ./var/counters/:/app/var/counters + depends_on: + - db + db: + image: postgres + container_name: db + env_file: + - .env + volumes: + - ./var/postgresql-data/:/var/lib/postgresql/data/ diff --git a/manage b/manage new file mode 100755 index 000000000..1077a0396 --- /dev/null +++ b/manage @@ -0,0 +1,14 @@ +#!/bin/sh +export UID=`id -u` +export GID=`id -g` + +if [ "$1" = "runserver" ] +then + PORT="$2" + [ -z "$PORT" ] && PORT=8000 + EXPOSED=127.0.0.1:"$PORT" + echo "expose as: $EXPOSED" + exec docker-compose run --rm -p "$EXPOSED":"$PORT" dev python $PYARGS manage.py runserver 0.0.0.0:"$PORT" +else + exec docker-compose run --rm dev python $PYARGS manage.py "$@" +fi diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 10498cb2b..084f0da8d 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -40,7 +40,7 @@ mutagen==1.47 sorl-thumbnail==12.10.0 # home-brewed & dependencies -librarian==24.5.7 +librarian==24.5.8 # celery tasks celery[redis]==5.4.0 diff --git a/src/api/urls.py b/src/api/urls.py index de3dba77f..62d4fa7f7 100644 --- a/src/api/urls.py +++ b/src/api/urls.py @@ -16,6 +16,7 @@ urlpatterns1 = [ path('login/', csrf_exempt(views.Login2View.as_view())), path('me/', views.UserView.as_view()), path('', include('catalogue.api.urls2')), + path('', include('social.api.urls2')), ] diff --git a/src/catalogue/api/serializers.py b/src/catalogue/api/serializers.py index 60e52a052..a90d2ef9c 100644 --- a/src/catalogue/api/serializers.py +++ b/src/catalogue/api/serializers.py @@ -156,6 +156,7 @@ class BookSerializer2(serializers.ModelSerializer): 'epub', 'mobi', 'pdf', 'html', 'txt', 'fb2', 'xml', 'cover_thumb', 'cover', 'isbn_pdf', 'isbn_epub', 'isbn_mobi', + 'abstract', ] class BookSerializer11Labs(serializers.ModelSerializer): @@ -352,6 +353,15 @@ class FragmentDetailSerializer(serializers.ModelSerializer): fields = ['book', 'anchor', 'text', 'url', 'themes'] +class FragmentSerializer2(serializers.ModelSerializer): + url = AbsoluteURLField() + html = serializers.CharField(source='text') + + class Meta: + model = Fragment + fields = ['anchor', 'html', 'url'] + + class FilterTagSerializer(serializers.ModelSerializer): class Meta: model = Tag diff --git a/src/catalogue/api/urls2.py b/src/catalogue/api/urls2.py index b16af6651..7dc131d23 100644 --- a/src/catalogue/api/urls2.py +++ b/src/catalogue/api/urls2.py @@ -19,6 +19,10 @@ urlpatterns = [ piwik_track_view(views.BookDetail2.as_view()), name='catalogue_api_book' ), + path('books//fragment/', + piwik_track_view(views.BookFragmentView.as_view()), + name='catalogue_api_book_fragment' + ), path('suggested-tags/', piwik_track_view(views.SuggestedTags.as_view()), diff --git a/src/catalogue/api/views.py b/src/catalogue/api/views.py index 0e758b15e..e45f80e75 100644 --- a/src/catalogue/api/views.py +++ b/src/catalogue/api/views.py @@ -501,6 +501,19 @@ class SuggestedTags(ListAPIView): def get_queryset(self): tag_ids = self.request.GET.getlist('tag', []) + search = self.request.GET.get('search') tags = [get_object_or_404(Tag, id=tid) for tid in tag_ids] related_tags = list(t.id for t in get_top_level_related_tags(tags)) - return Tag.objects.filter(id__in=related_tags) + tags = Tag.objects.filter(id__in=related_tags) + if search: + tags = tags.filter(name__icontains=search) + return tags + + +class BookFragmentView(RetrieveAPIView): + serializer_class = serializers.FragmentSerializer2 + + def get_object(self): + book = get_object_or_404(Book, slug=self.kwargs['slug']) + return book.choose_fragment() + diff --git a/src/catalogue/models/bookmedia.py b/src/catalogue/models/bookmedia.py index acb1881e5..0a1544fec 100644 --- a/src/catalogue/models/bookmedia.py +++ b/src/catalogue/models/bookmedia.py @@ -70,6 +70,8 @@ class BookMedia(models.Model): return f'{name}.{ext}' def save(self, parts_count=None, *args, **kwargs): + if self.type in ('daisy', 'audio.epub'): + return super().save(*args, **kwargs) from catalogue.utils import ExistingFile, remove_zip if not parts_count: diff --git a/src/club/civicrm.py b/src/club/civicrm.py index 20b287a28..dffeaa766 100644 --- a/src/club/civicrm.py +++ b/src/club/civicrm.py @@ -27,12 +27,12 @@ class CiviCRM: d = response.json() return d - def create_or_update_contact(self, email, key=None): + def create_or_update_contact(self, email, fields=None): contact_id = self.get_contact_id(email) if contact_id is None: - contact_id = self.create_contact(email, key) - elif key: - self.update_contact(contact_id, key) + contact_id = self.create_contact(email, fields) + elif fields: + self.update_contact(contact_id, fields) return contact_id def get_contact_id(self, email): @@ -49,7 +49,7 @@ class CiviCRM: if result: return result[0]['id'] - def create_contact(self, email, key=None): + def create_contact(self, email, fields): data = { 'values': {}, 'chain': { @@ -65,19 +65,22 @@ class CiviCRM: ] } } - if key: - data['values']['WL.TPWL_key'] = key + if fields: + data['values'].update(fields) result = self.request('Contact', 'create', data) return result['values'][0]['id'] - - def update_contact(self, contact_id, key): + + def update_phone(self, contact_id, phone): + if self.request('Phone', 'get', {'where': [['phone', "=", phone], ['contact_id', "=", contact_id]]})['count']: + return + self.request('Phone', 'create', {'values': {'phone': phone, 'contact_id': contact_id}}) + + def update_contact(self, contact_id, fields): return self.request( 'Contact', 'update', { - 'values': { - 'WL.TPWL_key': key, - }, + 'values': fields, 'where': [ ['id', '=', contact_id] ] @@ -89,7 +92,8 @@ class CiviCRM: if not self.enabled: return - contact_id = self.create_or_update_contact(email, tpwl_key) + fields = {'WL.TPWL_key': tpwl_key} + contact_id = self.create_or_update_contact(email, fields) activity_id = self.get_activity_id(key) if activity_id is None: diff --git a/src/social/api/urls2.py b/src/social/api/urls2.py new file mode 100644 index 000000000..b150e6183 --- /dev/null +++ b/src/social/api/urls2.py @@ -0,0 +1,17 @@ +# This file is part of Wolne Lektury, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Wolne Lektury. See NOTICE for more information. +# +from django.urls import path +from stats.utils import piwik_track_view +from . import views + + +urlpatterns = [ + path('like//', + piwik_track_view(views.LikeView2.as_view()), + name='social_api_like'), + path('likes/', views.LikesView.as_view()), + path('my-likes/', views.MyLikesView.as_view()), +] + + diff --git a/src/social/api/views.py b/src/social/api/views.py index a29930423..22a0e9c52 100644 --- a/src/social/api/views.py +++ b/src/social/api/views.py @@ -11,7 +11,9 @@ from api.utils import vary_on_auth from catalogue.api.helpers import order_books, books_after from catalogue.api.serializers import BookSerializer from catalogue.models import Book +import catalogue.models from social.utils import likes +from social.views import get_sets_for_book_ids @vary_on_auth @@ -32,6 +34,53 @@ class LikeView(APIView): return Response({}) +@vary_on_auth +class LikeView2(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, slug): + book = get_object_or_404(Book, slug=slug) + return Response({"likes": likes(request.user, book)}) + + def put(self, request, slug): + book = get_object_or_404(Book, slug=slug) + book.like(request.user) + return Response({"likes": likes(request.user, book)}) + + def delete(self, request, slug): + book = get_object_or_404(Book, slug=slug) + book.unlike(request.user) + return Response({"likes": likes(request.user, book)}) + + +@vary_on_auth +class LikesView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + slugs = request.GET.getlist('slug') + books = Book.objects.filter(slug__in=slugs) + books = {b.id: b.slug for b in books} + ids = books.keys() + res = get_sets_for_book_ids(ids, request.user) + res = {books[bid]: v for bid, v in res.items()} + return Response(res) + + +@vary_on_auth +class MyLikesView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + ids = catalogue.models.tag.TagRelation.objects.filter(tag__user=request.user).values_list('object_id', flat=True).distinct() + books = Book.objects.filter(id__in=ids) + books = {b.id: b.slug for b in books} + res = get_sets_for_book_ids(ids, request.user) + res = {books[bid]: v for bid, v in res.items()} + return Response(res) + + + @vary_on_auth class ShelfView(ListAPIView): permission_classes = [IsAuthenticated] diff --git a/src/wolnelektury/templates/admin/catalogue/book/change_list.html b/src/wolnelektury/templates/admin/catalogue/book/change_list.html index b9574d9c9..db3308460 100644 --- a/src/wolnelektury/templates/admin/catalogue/book/change_list.html +++ b/src/wolnelektury/templates/admin/catalogue/book/change_list.html @@ -4,7 +4,7 @@
{% csrf_token %}

- +