Compare commits

...

7 commits

Author SHA1 Message Date
Bart Schuurmans 4989fc359b
Merge 3963fa9579 into 304c47863b 2024-03-13 13:54:12 +00:00
Bart Schuurmans 3963fa9579 WIP: Add Series model 2024-03-13 14:54:01 +01:00
Bart Schuurmans 3ade7ca420 CI: merge all Python actions into one file 2024-03-13 13:19:12 +01:00
Bart Schuurmans ebba70db6e CI: use pytest-github-actions-annotate-failures 2024-03-13 12:36:11 +01:00
Bart Schuurmans d652183e33 CI: simplify pytest setup 2024-03-13 12:36:11 +01:00
Bart Schuurmans 39c811c76e CI: use actions/setup-python@v5 and cache pip 2024-03-13 12:35:23 +01:00
Bart Schuurmans 6096332f74 CI: use actions/checkout@v4 2024-03-13 12:30:52 +01:00
15 changed files with 310 additions and 190 deletions

View file

@ -1,17 +0,0 @@
name: Python Formatting (run ./bw-dev black to fix)
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- uses: psf/black@22.12.0
with:
version: 22.12.0

View file

@ -36,7 +36,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View file

@ -10,7 +10,7 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install curlylint
run: pip install curlylint

View file

@ -1,70 +0,0 @@
name: Run Python Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:13
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: hunter2
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Check migrations up-to-date
run: |
python ./manage.py makemigrations --check
env:
SECRET_KEY: beepbeep
DOMAIN: your.domain.here
EMAIL_HOST: ""
EMAIL_HOST_USER: ""
EMAIL_HOST_PASSWORD: ""
- name: Run Tests
env:
SECRET_KEY: beepbeep
DEBUG: false
USE_HTTPS: true
DOMAIN: your.domain.here
BOOKWYRM_DATABASE_BACKEND: postgres
MEDIA_ROOT: images/
POSTGRES_PASSWORD: hunter2
POSTGRES_USER: postgres
POSTGRES_DB: github_actions
POSTGRES_HOST: 127.0.0.1
CELERY_BROKER: ""
REDIS_BROKER_PORT: 6379
REDIS_BROKER_PASSWORD: beep
USE_DUMMY_CACHE: true
FLOWER_PORT: 8888
EMAIL_HOST: "smtp.mailgun.org"
EMAIL_PORT: 587
EMAIL_HOST_USER: ""
EMAIL_HOST_PASSWORD: ""
EMAIL_USE_TLS: true
ENABLE_PREVIEW_IMAGES: false
ENABLE_THUMBNAIL_GENERATION: true
HTTP_X_FORWARDED_PROTO: false
run: |
pytest -n 3

View file

@ -19,7 +19,7 @@ jobs:
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it.
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install modules
run: npm install stylelint stylelint-config-recommended stylelint-config-standard stylelint-order eslint

View file

@ -1,50 +0,0 @@
name: Mypy
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.9
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Analysing the code with mypy
env:
SECRET_KEY: beepbeep
DEBUG: false
USE_HTTPS: true
DOMAIN: your.domain.here
BOOKWYRM_DATABASE_BACKEND: postgres
MEDIA_ROOT: images/
POSTGRES_PASSWORD: hunter2
POSTGRES_USER: postgres
POSTGRES_DB: github_actions
POSTGRES_HOST: 127.0.0.1
CELERY_BROKER: ""
REDIS_BROKER_PORT: 6379
REDIS_BROKER_PASSWORD: beep
USE_DUMMY_CACHE: true
FLOWER_PORT: 8888
EMAIL_HOST: "smtp.mailgun.org"
EMAIL_PORT: 587
EMAIL_HOST_USER: ""
EMAIL_HOST_PASSWORD: ""
EMAIL_USE_TLS: true
ENABLE_PREVIEW_IMAGES: false
ENABLE_THUMBNAIL_GENERATION: true
HTTP_X_FORWARDED_PROTO: false
run: |
mypy bookwyrm celerywyrm

View file

@ -14,7 +14,7 @@ jobs:
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it.
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install modules
run: npm install prettier@2.5.1

View file

@ -1,27 +0,0 @@
name: Pylint
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.9
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Analysing the code with pylint
run: |
pylint bookwyrm/

99
.github/workflows/python.yml vendored Normal file
View file

@ -0,0 +1,99 @@
name: Python
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
# overrides for .env.example
env:
POSTGRES_HOST: 127.0.0.1
PGPORT: 5432
POSTGRES_USER: postgres
POSTGRES_PASSWORD: hunter2
POSTGRES_DB: github_actions
SECRET_KEY: beepbeep
EMAIL_HOST_USER: ""
EMAIL_HOST_PASSWORD: ""
jobs:
pytest:
name: Tests (pytest)
runs-on: ubuntu-latest
services:
postgres:
image: postgres:13
env: # does not inherit from jobs.build.env
POSTGRES_USER: postgres
POSTGRES_PASSWORD: hunter2
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: 3.9
cache: pip
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest-github-actions-annotate-failures
- name: Set up .env
run: cp .env.example .env
- name: Check migrations up-to-date
run: python ./manage.py makemigrations --check
- name: Run Tests
run: pytest -n 3
pylint:
name: Linting (pylint)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: 3.9
cache: pip
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Analyse code with pylint
run: pylint bookwyrm/
mypy:
name: Typing (mypy)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: 3.9
cache: pip
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Set up .env
run: cp .env.example .env
- name: Analyse code with mypy
run: mypy bookwyrm celerywyrm
black:
name: Formatting (black; run ./bw-dev black to fix)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: psf/black@22.12.0
with:
version: 22.12.0

View file

@ -1,4 +1,5 @@
""" using django model forms """
from django import forms
from file_resubmit.widgets import ResubmitImageWidget
@ -25,7 +26,6 @@ class EditionForm(CustomForm):
"subtitle",
"description",
"series",
"series_number",
"languages",
"subjects",
"publishers",
@ -55,9 +55,6 @@ class EditionForm(CustomForm):
attrs={"aria-describedby": "desc_description"}
),
"series": forms.TextInput(attrs={"aria-describedby": "desc_series"}),
"series_number": forms.TextInput(
attrs={"aria-describedby": "desc_series_number"}
),
"subjects": ArrayWidget(),
"languages": forms.TextInput(
attrs={"aria-describedby": "desc_languages_help desc_languages"}
@ -116,7 +113,6 @@ class EditionFromWorkForm(CustomForm):
"description",
"languages",
"series",
"series_number",
"subjects",
"subject_places",
"cover",

View file

@ -0,0 +1,148 @@
# Generated by Django 3.2.23 on 2024-02-04 20:27
import bookwyrm.models.fields
from django.db import migrations, models
import django.db.models.deletion
def make_series(apps, schema_editor):
Edition = apps.get_model("bookwyrm", "Edition")
Series = apps.get_model("bookwyrm", "Series")
SeriesBook = apps.get_model("bookwyrm", "SeriesBook")
db_alias = schema_editor.connection.alias
with_series = (
Edition.objects.using(db_alias)
.exclude(series_name__isnull=True)
.exclude(series_name__exact="")
.order_by("series_name", "series_number")
)
for edition in with_series:
# TODO: Try to parse number from series_name if series_number is empty?
series, _ = Series.objects.using(db_alias).get_or_create(
name=edition.series_name,
authors=edition.authors.all(),
)
SeriesBook.objects.using(db_alias).create(
book=edition, series=series, number=edition.series_number
)
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0195_alter_user_preferred_language"),
]
operations = [
migrations.RenameField(
model_name="book",
old_name="series",
new_name="series_name",
),
migrations.CreateModel(
name="Series",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_date", models.DateTimeField(auto_now_add=True)),
("updated_date", models.DateTimeField(auto_now=True)),
(
"remote_id",
bookwyrm.models.fields.RemoteIdField(
max_length=255,
null=True,
validators=[bookwyrm.models.fields.validate_remote_id],
),
),
("name", bookwyrm.models.fields.CharField(max_length=100)),
(
"authors",
bookwyrm.models.fields.ManyToManyField(to="bookwyrm.Author"),
),
],
options={
"abstract": False,
},
),
migrations.CreateModel(
name="SeriesBook",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_date", models.DateTimeField(auto_now_add=True)),
("updated_date", models.DateTimeField(auto_now=True)),
(
"remote_id",
bookwyrm.models.fields.RemoteIdField(
max_length=255,
null=True,
validators=[bookwyrm.models.fields.validate_remote_id],
),
),
(
"number",
bookwyrm.models.fields.CharField(
blank=True, max_length=255, null=True
),
),
(
"book",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.book"
),
),
(
"series",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="bookwyrm.series",
),
),
],
options={
"ordering": ["-number"],
"unique_together": {("book", "series")},
},
),
migrations.AddField(
model_name="series",
name="books",
field=bookwyrm.models.fields.ManyToManyField(
related_name="series_books",
through="bookwyrm.SeriesBook",
to="bookwyrm.Book",
),
),
migrations.AddField(
model_name="book",
name="series",
field=models.ManyToManyField(
through="bookwyrm.SeriesBook", to="bookwyrm.Series"
),
),
migrations.RunPython(make_series), # TODO: reverse_code
migrations.RemoveField(
model_name="book",
name="series_number",
),
migrations.RemoveField(
model_name="book",
name="series_name",
),
]

View file

@ -1,4 +1,5 @@
""" bring all the models into the app namespace """
import inspect
import sys
@ -7,6 +8,8 @@ from .author import Author
from .link import Link, FileLink, LinkDomain
from .connector import Connector
from .series import Series, SeriesBook
from .shelf import Shelf, ShelfBook
from .list import List, ListItem

View file

@ -1,4 +1,5 @@
""" database schema for books and shelves """
from itertools import chain
import re
from typing import Any
@ -120,8 +121,11 @@ class Book(BookDataModel):
languages = fields.ArrayField(
models.CharField(max_length=255), blank=True, default=list
)
series = fields.TextField(max_length=255, blank=True, null=True)
series_number = fields.CharField(max_length=255, blank=True, null=True)
series = models.ManyToManyField(
"Series",
through="SeriesBook",
through_fields=("book", "series"),
)
subjects = fields.ArrayField(
models.CharField(max_length=255), blank=True, null=True, default=list
)
@ -190,9 +194,13 @@ class Book(BookDataModel):
"""properties of this edition, as a string"""
items = [
self.physical_format if hasattr(self, "physical_format") else None,
f"{self.languages[0]} language"
if self.languages and self.languages[0] and self.languages[0] != "English"
else None,
(
f"{self.languages[0]} language"
if self.languages
and self.languages[0]
and self.languages[0] != "English"
else None
),
str(self.published_date.year) if self.published_date else None,
", ".join(self.publishers) if hasattr(self, "publishers") else None,
]

35
bookwyrm/models/series.py Normal file
View file

@ -0,0 +1,35 @@
"""series of books"""
from django.db import models
from .base_model import BookWyrmModel
from . import fields
class Series(BookWyrmModel):
"""a named series of books"""
name = fields.CharField(max_length=100)
authors = fields.ManyToManyField("Author") # TODO: add on Author model
books = fields.ManyToManyField(
"Book",
through="SeriesBook",
through_fields=("series", "book"),
related_name="series_books",
)
class SeriesBook(BookWyrmModel):
"""membership of a series"""
book = models.ForeignKey("Book", on_delete=models.PROTECT)
series = models.ForeignKey("Series", on_delete=models.PROTECT)
number = fields.CharField(max_length=255, blank=True, null=True)
collection_field = "series"
class Meta:
"""a series can't contain the same book twice"""
unique_together = ("book", "series")
ordering = ["-number"]

View file

@ -31,7 +31,7 @@
{{ book.title }}
</h1>
{% if book.subtitle or book.series %}
{% if book.subtitle or book.series.exists %}
<p class="subtitle title is-5">
{% if book.subtitle %}
<meta
@ -44,20 +44,15 @@
</span>
{% endif %}
{% if book.series %}
<meta itemprop="position" content="{{ book.series_number }}">
{% for book_series in book.series.all %}
<span itemprop="isPartOf" itemscope itemtype="https://schema.org/BookSeries">
{% if book.authors.exists %}
<a href="{% url 'book-series-by' book.authors.first.id %}?series_name={{ book.series | urlencode }}"
itemprop="url">
{% endif %}
<span itemprop="name">{{ book.series }}</span>
{% if book.series_number %} #{{ book.series_number }}{% endif %}
{% if book.authors.exists %}
<meta itemprop="position" content="{{ book_series.number }}">
<a href="{{ book_series.local_path }}" itemprop="url">
<span itemprop="name">{{ book_series.series.name }}</span>
</a>
{% endif %}
{% if book.series_number %} #{{ book.series_number }}{% endif %}
</span>
{% endif %}
{% endfor %}
</p>
{% endif %}