diff --git a/.github/workflows/songbook.yml b/.github/workflows/songbook.yml index 73c423f..3c00371 100644 --- a/.github/workflows/songbook.yml +++ b/.github/workflows/songbook.yml @@ -1,4 +1,4 @@ -name: Song Book +name: Acceptance on: pull_request: @@ -10,27 +10,19 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Set up Python 3.9 - uses: actions/setup-python@v1 + - uses: actions/checkout@v3 + - name: Install poetry + run: pip install poetry + - name: Setup Python + id: setup-python + uses: actions/setup-python@v4 with: - python-version: 3.9 - - name: Week number - id: weeknum - run: | - echo "::set-output name=weeknum::$(/bin/date -u "+%g.%V")" - shell: bash - - uses: actions/cache@v2 - with: - path: | - ~/.local/share/virtualenvs - Pipfile.lock - key: ${{ runner.os }}-w${{ steps.weeknum.outputs.weeknum }}-python-${{ steps.setup-python.outputs.python-version }}-pipenv-${{ hashFiles('Pipfile') }} + python-version: '3.11' + cache: "poetry" + cache-dependency-path: "pyproject.toml" - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install pipenv # Install all dependencies. - pipenv update --dev - - name: Lint with Pylint + poetry install + - name: Acceptance check run: | - ./pylint-check.sh + make commit-acceptance diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7849a3d --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +.PHONY: commit-acceptance pylint black reformat pre-commit locale migrations deploy + +commit-acceptance: black pylint +pre-commit: locale migrations + +pylint: + poetry run pylint backend/ chords/ pdf/ frontend/ category/ analytics/ --django-settings-module=chords.settings.development + +black: + poetry run black --check . --diff + +reformat: + poetry run black . + +locale: + poetry run python manage.py makemessages -l cs + +migrations: + poetry run python manage.py makemigrations + +deploy: + poetry run python manage.py migrate + poetry run python manage.py compilescss + poetry run python manage.py prerender + poetry run python manage.py compilemessages + poetry run python manage.py collectstatic --noinput + +# Check http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html +help: ## Print this help + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) + +# this ensures dependent target is run everytime +FORCE: \ No newline at end of file diff --git a/Pipfile b/Pipfile deleted file mode 100644 index e27697d..0000000 --- a/Pipfile +++ /dev/null @@ -1,33 +0,0 @@ -[[source]] -name = "pypi" -url = "https://pypi.org/simple" -verify_ssl = true - -[dev-packages] -pylint = "<2.15" -pylint-django = "*" -django-compressor = "*" -libsass = "*" - -[packages] -django = "==4.1.10" -django-bootstrap4 = "*" -dj-datatables-view = "*" -django-debug-toolbar = "*" -django-markdownx = ">=4.0.0b1" -django-redis = "*" -django-rq = "*" -django-sass-processor = "*" -django-simple-menu = "*" -django-weasyprint = "*" -weasyprint = ">=53.0" -Pillow = "*" -gunicorn = "*" -gevent = "*" -markdown = "*" -markdown3-newtab = "*" -netifaces = "*" -psycopg2-binary = "*" - -[requires] -python_version = "3" \ No newline at end of file diff --git a/analytics/apps.py b/analytics/apps.py index 78c7f48..784d5a3 100644 --- a/analytics/apps.py +++ b/analytics/apps.py @@ -4,5 +4,6 @@ class AnalyticsConfig(AppConfig): """Analytics app""" - default_auto_field = 'django.db.models.BigAutoField' - name = 'analytics' + + default_auto_field = "django.db.models.BigAutoField" + name = "analytics" diff --git a/analytics/locale/cs/LC_MESSAGES/django.po b/analytics/locale/cs/LC_MESSAGES/django.po index 9a383a4..8c66473 100644 --- a/analytics/locale/cs/LC_MESSAGES/django.po +++ b/analytics/locale/cs/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-06-12 07:19+0000\n" +"POT-Creation-Date: 2023-09-09 19:21+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -19,11 +19,11 @@ msgstr "" "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n " "<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" -#: analytics/models.py:8 +#: analytics/models.py:9 msgid "Key" msgstr "Klíč" -#: analytics/models.py:9 +#: analytics/models.py:10 msgid "Hits" msgstr "Kliknutí" @@ -59,6 +59,6 @@ msgstr "Měsíc" msgid "Year" msgstr "Rok" -#: analytics/views.py:31 +#: analytics/views.py:30 msgid "General" msgstr "Obecné" diff --git a/analytics/migrations/0001_initial.py b/analytics/migrations/0001_initial.py index 598efc7..3c62fd8 100644 --- a/analytics/migrations/0001_initial.py +++ b/analytics/migrations/0001_initial.py @@ -4,20 +4,26 @@ class Migration(migrations.Migration): - initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='DayStatistic', + name="DayStatistic", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('key', models.CharField(max_length=25, verbose_name='Key')), - ('amount', models.IntegerField(default=0, verbose_name='Amount')), - ('date', models.DateField(auto_now_add=True)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("key", models.CharField(max_length=25, verbose_name="Key")), + ("amount", models.IntegerField(default=0, verbose_name="Amount")), + ("date", models.DateField(auto_now_add=True)), ], ), ] diff --git a/analytics/migrations/0002_auto_20211212_0917.py b/analytics/migrations/0002_auto_20211212_0917.py index a0e6ebc..9501e3d 100644 --- a/analytics/migrations/0002_auto_20211212_0917.py +++ b/analytics/migrations/0002_auto_20211212_0917.py @@ -4,19 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('analytics', '0001_initial'), + ("analytics", "0001_initial"), ] operations = [ migrations.RemoveField( - model_name='daystatistic', - name='amount', + model_name="daystatistic", + name="amount", ), migrations.AddField( - model_name='daystatistic', - name='hits', - field=models.IntegerField(default=0, verbose_name='Hits'), + model_name="daystatistic", + name="hits", + field=models.IntegerField(default=0, verbose_name="Hits"), ), ] diff --git a/analytics/models.py b/analytics/models.py index aacb6db..b1b5f36 100644 --- a/analytics/models.py +++ b/analytics/models.py @@ -5,6 +5,7 @@ class DayStatistic(Model): """Statistic data for given day/key combination""" + key = CharField(verbose_name=_("Key"), max_length=25) hits = IntegerField(verbose_name=_("Hits"), default=0) date = DateField(auto_now_add=True, editable=False) diff --git a/analytics/urls.py b/analytics/urls.py index 554afa5..a1cb2df 100644 --- a/analytics/urls.py +++ b/analytics/urls.py @@ -4,6 +4,6 @@ from analytics.views import AnalyticsShowView, AnalyticsRestView urlpatterns = [ - path('', AnalyticsShowView.as_view(), name="index"), - path('data', AnalyticsRestView.as_view(), name="data"), + path("", AnalyticsShowView.as_view(), name="index"), + path("data", AnalyticsRestView.as_view(), name="data"), ] diff --git a/analytics/views.py b/analytics/views.py index ae0e373..7e17c57 100644 --- a/analytics/views.py +++ b/analytics/views.py @@ -5,6 +5,7 @@ from django.db import transaction from django.db.models import Sum from django.http import HttpResponseBadRequest, HttpRequest, JsonResponse + # Create your views here. from django.utils.decorators import method_decorator from django.utils.timezone import now @@ -18,16 +19,14 @@ def analytics(key): """Adds hit""" with transaction.atomic(): - statistic, _ = DayStatistic.objects.get_or_create( - key=key, - date=now() - ) + statistic, _ = DayStatistic.objects.get_or_create(key=key, date=now()) statistic.hits += 1 statistic.save() class AnalyticsMixin(View): """Mixin for gathering number of hits per day analytics""" + KEY = gettext_noop("General") def get_key(self): @@ -40,14 +39,15 @@ def dispatch(self, request, *args, **kwargs): return result -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class AnalyticsShowView(TemplateView): """Shows analytics graphs""" + template_name = "analytics/show.html" def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) - ctx["keys"] = DayStatistic.objects.order_by('key').values_list('key', flat=True).distinct() + ctx["keys"] = DayStatistic.objects.order_by("key").values_list("key", flat=True).distinct() ctx["now"] = datetime.now().date() ctx["week"] = (datetime.now() - timedelta(days=6)).date() # ctx["day"] = (datetime.now() - timedelta(days=1)).date() @@ -56,7 +56,7 @@ def get_context_data(self, **kwargs): return ctx -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class AnalyticsRestView(View): """Returns analytics data for given dates and key""" @@ -74,5 +74,5 @@ def get(self, request: HttpRequest, *args, **kwargs): days = DayStatistic.objects.filter(date__gte=start_date, date__lte=end_date, key=key) else: days = DayStatistic.objects.filter(date__gte=start_date, date__lte=end_date) - days = days.values("date").annotate(total=Sum('hits')).values("date", "total").order_by("date") + days = days.values("date").annotate(total=Sum("hits")).values("date", "total").order_by("date") return JsonResponse({entry["date"].isoformat(): entry["total"] for entry in days}) diff --git a/backend/apps.py b/backend/apps.py index c716cdc..b38e2ec 100644 --- a/backend/apps.py +++ b/backend/apps.py @@ -4,4 +4,5 @@ class BackendConfig(AppConfig): """Backend AppConfig""" - name = 'backend' + + name = "backend" diff --git a/backend/forms.py b/backend/forms.py index dfa758a..8ff904a 100644 --- a/backend/forms.py +++ b/backend/forms.py @@ -7,6 +7,7 @@ class SongForm(ModelForm): """Song form""" + categories = ModelMultipleChoiceField(widget=CheckboxSelectMultiple, queryset=Category.objects.all()) class Meta: diff --git a/backend/locale/cs/LC_MESSAGES/django.po b/backend/locale/cs/LC_MESSAGES/django.po index 844b3e2..009688b 100644 --- a/backend/locale/cs/LC_MESSAGES/django.po +++ b/backend/locale/cs/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-09-26 12:40+0000\n" +"POT-Creation-Date: 2023-09-09 19:21+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -23,71 +23,71 @@ msgstr "" msgid "Songbook List" msgstr "Seznam zpěvníků" -#: backend/menus.py:10 +#: backend/menus.py:9 msgid "Add a song" msgstr "Přidat písničku" -#: backend/menus.py:12 +#: backend/menus.py:10 msgid "Add Songbook" msgstr "Přidat zpěvník" -#: backend/menus.py:14 +#: backend/menus.py:11 msgid "Analytics" msgstr "Statistiky" -#: backend/menus.py:18 +#: backend/menus.py:17 msgid "Admin" msgstr "Administrace" -#: backend/menus.py:24 +#: backend/menus.py:26 msgid "Log in" msgstr "Přihlásit se" -#: backend/menus.py:27 +#: backend/menus.py:31 msgid "Logout" msgstr "Odhlásit se" -#: backend/menus.py:30 +#: backend/menus.py:36 msgid "Change password" msgstr "Změna hesla" -#: backend/menus.py:35 +#: backend/menus.py:42 msgid "Account" msgstr "Účet" -#: backend/models.py:22 +#: backend/models.py:32 msgid "Name" msgstr "Název písničky" -#: backend/models.py:24 +#: backend/models.py:34 msgid "Capo" msgstr "" -#: backend/models.py:25 +#: backend/models.py:35 msgid "Author" msgstr "Autor" -#: backend/models.py:26 +#: backend/models.py:36 msgid "Youtube Link" msgstr "Odkaz na Youtube" -#: backend/models.py:27 +#: backend/models.py:37 msgid "Songbooks" msgstr "Seznam zpěvníků" -#: backend/models.py:28 +#: backend/models.py:38 msgid "Archived" msgstr "Archivovat" -#: backend/models.py:29 +#: backend/models.py:39 msgid "Lyrics" msgstr "Text" -#: backend/models.py:81 +#: backend/models.py:91 msgid "Song" msgstr "Písnička" -#: backend/models.py:82 +#: backend/models.py:92 msgid "Songs" msgstr "Písničky" @@ -135,21 +135,21 @@ msgstr "Hledat" msgid "Number, text or author" msgstr "Číslo, text nebo autor" -#: backend/views.py:74 +#: backend/views.py:93 msgid "Index Page" msgstr "Úvodní stránka" -#: backend/views.py:93 +#: backend/views.py:113 #, python-format msgid "Song %(name)s was successfully created" msgstr "Písnička %(name)s byla úspěšně přidána" -#: backend/views.py:109 +#: backend/views.py:130 #, python-format msgid "Song %(name)s was successfully updated" msgstr "Písnička %(name)s byla úspěšně upravena" -#: backend/views.py:137 +#: backend/views.py:147 #, python-format msgid "Song %s was successfully deleted" msgstr "Písnička %s byla úspěšně odstraněna" diff --git a/backend/management/commands/clear_prerender.py b/backend/management/commands/clear_prerender.py index 6087b83..cec278a 100644 --- a/backend/management/commands/clear_prerender.py +++ b/backend/management/commands/clear_prerender.py @@ -6,6 +6,7 @@ class Command(BaseCommand): """Erases all prerendered html from all songs""" + def handle(self, *args, **options): Song.objects.all().update(prerendered_pdf=None, prerendered_web=None) print("Removed all prerendered markdowns") diff --git a/backend/management/commands/prerender.py b/backend/management/commands/prerender.py index ff780b6..777f765 100644 --- a/backend/management/commands/prerender.py +++ b/backend/management/commands/prerender.py @@ -6,6 +6,7 @@ class Command(BaseCommand): """Generates prerendered html from markdown for all songs""" + def handle(self, *args, **options): bulk = [] for song in Song.objects.all(): diff --git a/backend/menus.py b/backend/menus.py index 6601d70..3102130 100644 --- a/backend/menus.py +++ b/backend/menus.py @@ -5,33 +5,38 @@ from menu import MenuItem, Menu admin_children = ( - MenuItem(_("Songbook List"), - reverse("category:list")), - MenuItem(_("Add a song"), - reverse("backend:add")), - MenuItem(_("Add Songbook"), - reverse("category:add")), - MenuItem(_("Analytics"), - reverse("analytics:index")) + MenuItem(_("Songbook List"), reverse("category:list")), + MenuItem(_("Add a song"), reverse("backend:add")), + MenuItem(_("Add Songbook"), reverse("category:add")), + MenuItem(_("Analytics"), reverse("analytics:index")), ) -Menu.add_item("admin", MenuItem(_("Admin"), - reverse("backend:index"), - children=admin_children, - check=lambda request: request.user.is_authenticated)) +Menu.add_item( + "admin", + MenuItem( + _("Admin"), + reverse("backend:index"), + children=admin_children, + check=lambda request: request.user.is_authenticated, + ), +) account_children = ( - MenuItem(_("Log in"), - reverse("login"), - check=lambda request: not request.user.is_authenticated), - MenuItem(_("Logout"), - reverse("logout"), - check=lambda request: request.user.is_authenticated), - MenuItem(_("Change password"), - reverse("password_change"), - check=lambda request: request.user.is_authenticated) + MenuItem( + _("Log in"), + reverse("login"), + check=lambda request: not request.user.is_authenticated, + ), + MenuItem( + _("Logout"), + reverse("logout"), + check=lambda request: request.user.is_authenticated, + ), + MenuItem( + _("Change password"), + reverse("password_change"), + check=lambda request: request.user.is_authenticated, + ), ) -Menu.add_item("account", MenuItem(_("Account"), - reverse("login"), - children=account_children)) +Menu.add_item("account", MenuItem(_("Account"), reverse("login"), children=account_children)) diff --git a/backend/middleware/cache.py b/backend/middleware/cache.py index 4a20f3e..8ae73f3 100644 --- a/backend/middleware/cache.py +++ b/backend/middleware/cache.py @@ -4,6 +4,7 @@ class DisableClientSideCachingMiddleware: """Disables client side caching""" + def __init__(self, get_response): self.get_response = get_response diff --git a/backend/middleware/settings.py b/backend/middleware/settings.py index 10f540a..9577a88 100644 --- a/backend/middleware/settings.py +++ b/backend/middleware/settings.py @@ -4,6 +4,7 @@ class SiteNameMiddleware: """Adds SITE_NAME setting to the request""" + def __init__(self, get_response): self.get_response = get_response @@ -15,6 +16,7 @@ def __call__(self, request): class CacheTimeoutMiddleware: """Adds CACHE_TIMEOUT setting to the request""" + def __init__(self, get_response): self.get_response = get_response diff --git a/backend/migrations/0001_initial.py b/backend/migrations/0001_initial.py index 5cffdfb..16eda58 100644 --- a/backend/migrations/0001_initial.py +++ b/backend/migrations/0001_initial.py @@ -6,27 +6,51 @@ class Migration(migrations.Migration): - initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Song', + name="Song", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100, verbose_name='Song Name')), - ('song_number', models.PositiveIntegerField(unique=True, verbose_name='Song Number')), - ('author', models.CharField(max_length=100, null=True, verbose_name='Author')), - ('link', models.URLField(null=True, verbose_name='Youtube Link')), - ('locale', models.CharField(max_length=5, validators=[django.core.validators.RegexValidator('[a-z]{2}|[a-z]{2}_[a-z]{2})', 'Your language code should in format ab_cd')], verbose_name='Language')), - ('text', markdownx.models.MarkdownxField(verbose_name='Lyrics')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=100, verbose_name="Song Name")), + ( + "song_number", + models.PositiveIntegerField(unique=True, verbose_name="Song Number"), + ), + ( + "author", + models.CharField(max_length=100, null=True, verbose_name="Author"), + ), + ("link", models.URLField(null=True, verbose_name="Youtube Link")), + ( + "locale", + models.CharField( + max_length=5, + validators=[ + django.core.validators.RegexValidator( + "[a-z]{2}|[a-z]{2}_[a-z]{2})", + "Your language code should in format ab_cd", + ) + ], + verbose_name="Language", + ), + ), + ("text", markdownx.models.MarkdownxField(verbose_name="Lyrics")), ], options={ - 'verbose_name': 'Song', - 'verbose_name_plural': 'Songs', + "verbose_name": "Song", + "verbose_name_plural": "Songs", }, ), ] diff --git a/backend/migrations/0002_auto_20191109_0858.py b/backend/migrations/0002_auto_20191109_0858.py index 3cddd90..c0b09d2 100644 --- a/backend/migrations/0002_auto_20191109_0858.py +++ b/backend/migrations/0002_auto_20191109_0858.py @@ -5,25 +5,33 @@ class Migration(migrations.Migration): - dependencies = [ - ('backend', '0001_initial'), + ("backend", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='song', - name='author', - field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Author'), + model_name="song", + name="author", + field=models.CharField(blank=True, max_length=100, null=True, verbose_name="Author"), ), migrations.AlterField( - model_name='song', - name='link', - field=models.URLField(blank=True, null=True, verbose_name='Youtube Link'), + model_name="song", + name="link", + field=models.URLField(blank=True, null=True, verbose_name="Youtube Link"), ), migrations.AlterField( - model_name='song', - name='locale', - field=models.CharField(max_length=5, validators=[django.core.validators.RegexValidator('([a-z]{2}|[a-z]{2}_[a-z]{2})', 'Your language code should in format ab_cd')], verbose_name='Language'), + model_name="song", + name="locale", + field=models.CharField( + max_length=5, + validators=[ + django.core.validators.RegexValidator( + "([a-z]{2}|[a-z]{2}_[a-z]{2})", + "Your language code should in format ab_cd", + ) + ], + verbose_name="Language", + ), ), ] diff --git a/backend/migrations/0003_auto_20191109_0912.py b/backend/migrations/0003_auto_20191109_0912.py index 0a13dda..9e190fb 100644 --- a/backend/migrations/0003_auto_20191109_0912.py +++ b/backend/migrations/0003_auto_20191109_0912.py @@ -5,19 +5,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('backend', '0002_auto_20191109_0858'), + ("backend", "0002_auto_20191109_0858"), ] operations = [ migrations.RemoveField( - model_name='song', - name='song_number', + model_name="song", + name="song_number", ), migrations.AddField( - model_name='song', - name='date', + model_name="song", + name="date", field=models.DateField(auto_now_add=True, default=django.utils.timezone.now), preserve_default=False, ), diff --git a/backend/migrations/0004_auto_20191109_1019.py b/backend/migrations/0004_auto_20191109_1019.py index 8dd419b..c1da980 100644 --- a/backend/migrations/0004_auto_20191109_1019.py +++ b/backend/migrations/0004_auto_20191109_1019.py @@ -4,15 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('backend', '0003_auto_20191109_0912'), + ("backend", "0003_auto_20191109_0912"), ] operations = [ migrations.AlterField( - model_name='song', - name='locale', - field=models.CharField(choices=[('en', 'English'), ('cs', 'Czech')], max_length=5, verbose_name='Language'), + model_name="song", + name="locale", + field=models.CharField( + choices=[("en", "English"), ("cs", "Czech")], + max_length=5, + verbose_name="Language", + ), ), ] diff --git a/backend/migrations/0005_auto_20191124_1327.py b/backend/migrations/0005_auto_20191124_1327.py index c0b70dc..0f142d2 100644 --- a/backend/migrations/0005_auto_20191124_1327.py +++ b/backend/migrations/0005_auto_20191124_1327.py @@ -4,20 +4,24 @@ class Migration(migrations.Migration): - dependencies = [ - ('backend', '0004_auto_20191109_1019'), + ("backend", "0004_auto_20191109_1019"), ] operations = [ migrations.AddField( - model_name='song', - name='capo', - field=models.PositiveSmallIntegerField(default=0, verbose_name='Capo'), + model_name="song", + name="capo", + field=models.PositiveSmallIntegerField(default=0, verbose_name="Capo"), ), migrations.AlterField( - model_name='song', - name='locale', - field=models.CharField(choices=[('en', 'English'), ('cs', 'Czech')], default='Czech', max_length=5, verbose_name='Language'), + model_name="song", + name="locale", + field=models.CharField( + choices=[("en", "English"), ("cs", "Czech")], + default="Czech", + max_length=5, + verbose_name="Language", + ), ), ] diff --git a/backend/migrations/0006_category_20200809_0529.py b/backend/migrations/0006_category_20200809_0529.py index a70d8dd..2973c9b 100644 --- a/backend/migrations/0006_category_20200809_0529.py +++ b/backend/migrations/0006_category_20200809_0529.py @@ -4,9 +4,9 @@ def migrate_locales(apps, schema_editor): - Song = apps.get_model('backend', 'Song') - Category = apps.get_model('category', 'Category') - locales = map(lambda x: x['locale'], Song.objects.values('locale').distinct()) + Song = apps.get_model("backend", "Song") + Category = apps.get_model("category", "Category") + locales = map(lambda x: x["locale"], Song.objects.values("locale").distinct()) for locale in locales: category = Category(name=locale, generate_pdf=True, slug=locale, locale=locale) category.save() @@ -25,21 +25,20 @@ def migrate_locales(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ - ('category', '0001_initial'), - ('backend', '0005_auto_20191124_1327'), + ("category", "0001_initial"), + ("backend", "0005_auto_20191124_1327"), ] operations = [ migrations.AddField( - model_name='song', - name='categories', - field=models.ManyToManyField(to='category.Category'), + model_name="song", + name="categories", + field=models.ManyToManyField(to="category.Category"), ), migrations.RunPython(migrate_locales), migrations.RemoveField( - model_name='song', - name='locale', + model_name="song", + name="locale", ), ] diff --git a/backend/migrations/0007_auto_20200918_1217.py b/backend/migrations/0007_auto_20200918_1217.py index 6915824..5153a2c 100644 --- a/backend/migrations/0007_auto_20200918_1217.py +++ b/backend/migrations/0007_auto_20200918_1217.py @@ -4,19 +4,22 @@ class Migration(migrations.Migration): - dependencies = [ - ('backend', '0006_category_20200809_0529'), + ("backend", "0006_category_20200809_0529"), ] operations = [ migrations.AlterModelOptions( - name='song', - options={'ordering': ['date'], 'verbose_name': 'Song', 'verbose_name_plural': 'Songs'}, + name="song", + options={ + "ordering": ["date"], + "verbose_name": "Song", + "verbose_name_plural": "Songs", + }, ), migrations.AlterField( - model_name='song', - name='name', - field=models.CharField(max_length=100, verbose_name='Name'), + model_name="song", + name="name", + field=models.CharField(max_length=100, verbose_name="Name"), ), ] diff --git a/backend/migrations/0008_auto_20201018_0833.py b/backend/migrations/0008_auto_20201018_0833.py index a655733..07f7a30 100644 --- a/backend/migrations/0008_auto_20201018_0833.py +++ b/backend/migrations/0008_auto_20201018_0833.py @@ -4,14 +4,17 @@ class Migration(migrations.Migration): - dependencies = [ - ('backend', '0007_auto_20200918_1217'), + ("backend", "0007_auto_20200918_1217"), ] operations = [ migrations.AlterModelOptions( - name='song', - options={'ordering': ['date', 'id'], 'verbose_name': 'Song', 'verbose_name_plural': 'Songs'}, + name="song", + options={ + "ordering": ["date", "id"], + "verbose_name": "Song", + "verbose_name_plural": "Songs", + }, ), ] diff --git a/backend/migrations/0009_auto_20210131_1025.py b/backend/migrations/0009_auto_20210131_1025.py index e6db39d..7bd877a 100644 --- a/backend/migrations/0009_auto_20210131_1025.py +++ b/backend/migrations/0009_auto_20210131_1025.py @@ -4,26 +4,25 @@ class Migration(migrations.Migration): - dependencies = [ - ('category', '0003_auto_20200925_1902'), - ('backend', '0008_auto_20201018_0833'), + ("category", "0003_auto_20200925_1902"), + ("backend", "0008_auto_20201018_0833"), ] operations = [ migrations.AddField( - model_name='song', - name='prerendered_pdf', + model_name="song", + name="prerendered_pdf", field=models.TextField(null=True), ), migrations.AddField( - model_name='song', - name='prerendered_web', + model_name="song", + name="prerendered_web", field=models.TextField(null=True), ), migrations.AlterField( - model_name='song', - name='categories', - field=models.ManyToManyField(to='category.Category', verbose_name='Categories'), + model_name="song", + name="categories", + field=models.ManyToManyField(to="category.Category", verbose_name="Categories"), ), ] diff --git a/backend/migrations/0010_alter_song_categories.py b/backend/migrations/0010_alter_song_categories.py index eda0159..f0abdcc 100644 --- a/backend/migrations/0010_alter_song_categories.py +++ b/backend/migrations/0010_alter_song_categories.py @@ -4,16 +4,15 @@ class Migration(migrations.Migration): - dependencies = [ - ('category', '0004_alter_category_options'), - ('backend', '0009_auto_20210131_1025'), + ("category", "0004_alter_category_options"), + ("backend", "0009_auto_20210131_1025"), ] operations = [ migrations.AlterField( - model_name='song', - name='categories', - field=models.ManyToManyField(to='category.Category', verbose_name='Songbooks'), + model_name="song", + name="categories", + field=models.ManyToManyField(to="category.Category", verbose_name="Songbooks"), ), ] diff --git a/backend/migrations/0011_song_archived.py b/backend/migrations/0011_song_archived.py index 6ae49a2..032ae35 100644 --- a/backend/migrations/0011_song_archived.py +++ b/backend/migrations/0011_song_archived.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('backend', '0010_alter_song_categories'), + ("backend", "0010_alter_song_categories"), ] operations = [ migrations.AddField( - model_name='song', - name='archived', - field=models.BooleanField(default=False, verbose_name='Archived'), + model_name="song", + name="archived", + field=models.BooleanField(default=False, verbose_name="Archived"), ), ] diff --git a/backend/models.py b/backend/models.py index af1ff2d..625b29d 100644 --- a/backend/models.py +++ b/backend/models.py @@ -2,8 +2,16 @@ from enum import Enum from django.conf import settings -from django.db.models import Model, CharField, URLField, DateField, PositiveSmallIntegerField, ManyToManyField, \ - TextField, BooleanField +from django.db.models import ( + Model, + CharField, + URLField, + DateField, + PositiveSmallIntegerField, + ManyToManyField, + TextField, + BooleanField, +) from django.utils.translation import gettext_lazy as _ from markdownx.models import MarkdownxField @@ -13,12 +21,14 @@ class MarkdownTypes(Enum): """Type of markdown (WEB or PDF)""" + WEB = 0 PDF = 1 class Song(Model): """Song model""" + name = CharField(verbose_name=_("Name"), max_length=100) date = DateField(auto_now_add=True, editable=False) capo = PositiveSmallIntegerField(verbose_name=_("Capo"), default=0) @@ -78,13 +88,20 @@ def rendered_pdf_markdown(self): return self._get_rendered_markdown(pdf_markdown, MarkdownTypes.PDF) class Meta: - verbose_name = _('Song') - verbose_name_plural = _('Songs') - ordering = ['date', 'id'] + verbose_name = _("Song") + verbose_name_plural = _("Songs") + ordering = ["date", "id"] def __hash__(self): - values = [self.name, self.date, self.capo, self.author, - self.link, self.categories, self.text] + values = [ + self.name, + self.date, + self.capo, + self.author, + self.link, + self.categories, + self.text, + ] if hasattr(self, "song_number"): # pylint: disable=no-member values.append(self.song_number) diff --git a/backend/templatetags/markdown.py b/backend/templatetags/markdown.py index 09fe68d..c5e22ee 100644 --- a/backend/templatetags/markdown.py +++ b/backend/templatetags/markdown.py @@ -3,7 +3,10 @@ from django.conf import settings from django.template.defaultfilters import stringfilter from markdown import markdown, Markdown -from markdownx.settings import MARKDOWNX_MARKDOWN_EXTENSIONS, MARKDOWNX_MARKDOWN_EXTENSION_CONFIGS +from markdownx.settings import ( + MARKDOWNX_MARKDOWN_EXTENSIONS, + MARKDOWNX_MARKDOWN_EXTENSION_CONFIGS, +) def create_markdown(extensions): @@ -11,10 +14,7 @@ def create_markdown(extensions): if extensions is None: extensions = MARKDOWNX_MARKDOWN_EXTENSIONS - return Markdown( - extensions=extensions, - extension_configs=MARKDOWNX_MARKDOWN_EXTENSION_CONFIGS - ) + return Markdown(extensions=extensions, extension_configs=MARKDOWNX_MARKDOWN_EXTENSION_CONFIGS) register = template.Library() @@ -42,5 +42,5 @@ def markdownify(content, extensions=None): return markdown( text=content, extensions=extensions, - extension_configs=MARKDOWNX_MARKDOWN_EXTENSION_CONFIGS + extension_configs=MARKDOWNX_MARKDOWN_EXTENSION_CONFIGS, ) diff --git a/backend/urls.py b/backend/urls.py index 933e8b5..3f9bda4 100644 --- a/backend/urls.py +++ b/backend/urls.py @@ -1,13 +1,18 @@ """Backend app URL Configuration""" from django.urls import path -from backend.views import SongCreateView, SongUpdateView, SongDeleteView, SongsDatatableView, \ - IndexSongListView +from backend.views import ( + SongCreateView, + SongUpdateView, + SongDeleteView, + SongsDatatableView, + IndexSongListView, +) urlpatterns = [ - path('', IndexSongListView.as_view(), name="index"), - path('add', SongCreateView.as_view(), name="add"), - path('edit/', SongUpdateView.as_view(), name="edit"), - path('delete/', SongDeleteView.as_view(), name="delete"), - path('api/songs', SongsDatatableView.as_view(), name="songs"), + path("", IndexSongListView.as_view(), name="index"), + path("add", SongCreateView.as_view(), name="add"), + path("edit/", SongUpdateView.as_view(), name="edit"), + path("delete/", SongDeleteView.as_view(), name="delete"), + path("api/songs", SongsDatatableView.as_view(), name="songs"), ] diff --git a/backend/views.py b/backend/views.py index dff52d8..8a6d833 100644 --- a/backend/views.py +++ b/backend/views.py @@ -26,17 +26,18 @@ def transform_song(song: Song, number: int, authenticated: bool) -> Dict: transformed = model_to_dict(song, ["id", "name", "capo", "author", "link", "archived"]) transformed["number"] = number if authenticated: - transformed['edit_url'] = reverse("chords:edit", kwargs={"pk": song.id}) - transformed['delete_url'] = reverse("chords:delete", kwargs={"pk": song.id}) + transformed["edit_url"] = reverse("chords:edit", kwargs={"pk": song.id}) + transformed["delete_url"] = reverse("chords:delete", kwargs={"pk": song.id}) transformed["text"] = song.rendered_web_markdown return transformed class SongListView(ListView): """Lists songs in the one page application""" + model = Song - template_name = 'songs/index.html' - context_object_name = 'songs' + template_name = "songs/index.html" + context_object_name = "songs" FIELDS = ["id", "name", "capo", "author", "link", "prerendered_web"] @@ -65,12 +66,13 @@ def get_context_data(self, *, object_list=None, **kwargs): songs.append(transform_song(song, i, authenticated)) i += 1 - context_data['songs'] = json.dumps(songs, cls=DjangoJSONEncoder) + context_data["songs"] = json.dumps(songs, cls=DjangoJSONEncoder) return context_data class RegenerateViewMixin: """Mixin which tell you if the object changed or not""" + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.regenerate = None @@ -87,6 +89,7 @@ def form_valid(self, form): class IndexSongListView(SongListView, AnalyticsMixin): """Shows first available category""" + KEY = gettext_noop("Index Page") def get_queryset(self): @@ -100,12 +103,13 @@ def get_queryset(self): return super().get_queryset() -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class SongCreateView(SuccessMessageMixin, CreateView): """Creates new song""" + form_class = SongForm model = Song - template_name = 'songs/add.html' + template_name = "songs/add.html" success_message = _("Song %(name)s was successfully created") success_url = reverse_lazy("backend:index") @@ -115,12 +119,13 @@ def get_success_url(self): return super().get_success_url() -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class SongUpdateView(SuccessMessageMixin, RegenerateViewMixin, UpdateView): """Updates existing song""" + form_class = SongForm model = Song - template_name = 'songs/add.html' + template_name = "songs/add.html" success_url = reverse_lazy("backend:index") success_message = _("Song %(name)s was successfully updated") @@ -132,9 +137,10 @@ def post(self, request, *args, **kwargs): return response -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class SongDeleteView(DeleteView): """Removes song""" + model = Song template_name = "songs/confirm_delete.html" success_url = reverse_lazy("backend:index") @@ -147,9 +153,10 @@ def post(self, request, *args, **kwargs): return response -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class SongsDatatableView(BaseDatatableView): """API for datatables that returns all songs""" + model = Song max_display_length = 500 columns = ["name", "author", "capo"] diff --git a/category/apps.py b/category/apps.py index b7dfabd..3fec6ac 100644 --- a/category/apps.py +++ b/category/apps.py @@ -4,4 +4,5 @@ class CategoryConfig(AppConfig): """Category configuration""" - name = 'category' + + name = "category" diff --git a/category/forms.py b/category/forms.py index df5d97d..b1ab871 100644 --- a/category/forms.py +++ b/category/forms.py @@ -9,4 +9,4 @@ class CategoryForm(ModelForm): class Meta: model = Category - fields = '__all__' + fields = "__all__" diff --git a/category/locale/cs/LC_MESSAGES/django.po b/category/locale/cs/LC_MESSAGES/django.po index 1aa7850..90abdb3 100644 --- a/category/locale/cs/LC_MESSAGES/django.po +++ b/category/locale/cs/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-09-29 20:07+0000\n" +"POT-Creation-Date: 2023-09-09 19:21+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -19,24 +19,24 @@ msgstr "" "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n " "<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" -#: category/menus.py:18 category/templates/category/list.html:4 +#: category/menus.py:26 category/templates/category/list.html:4 #: category/templates/category/list.html:5 msgid "Songbooks" msgstr "Zpěvníky" -#: category/models.py:10 category/templates/category/list.html:11 +#: category/models.py:11 category/templates/category/list.html:11 msgid "Name" msgstr "Jméno" -#: category/models.py:11 category/templates/category/list.html:12 +#: category/models.py:12 category/templates/category/list.html:12 msgid "URL pattern" msgstr "URL vzor" -#: category/models.py:12 category/templates/category/list.html:13 +#: category/models.py:14 category/templates/category/list.html:13 msgid "PDF generation" msgstr "Automatické Generování PDF" -#: category/models.py:13 +#: category/models.py:15 msgid "Should the PDF file be automatically generated when a song changes?" msgstr "" "Má být vygenerován pdf soubor když se nějaká písnička ze zpěvníku změní?" @@ -96,23 +96,22 @@ msgstr "Již ve frontě" msgid "Songbook on url /%(slug)s does not exists" msgstr "Zpěvník s url /%(slug)s neexistuje" -#: category/views.py:59 +#: category/views.py:61 #, python-format msgid "Songbook %(name)s was successfully created" msgstr "Zpěvník %(name)s byla úspěšně vytvořena" -#: category/views.py:73 +#: category/views.py:76 #, python-format msgid "Songbook %(name)s was successfully updated" msgstr "Zpěvník %(name)s byla úspěšně upravena" -#: category/views.py:92 +#: category/views.py:98 #, python-format msgid "Category %s was successfully staged for PDF generation" msgstr "PDF pro zpěvník %s bylo přidané do fronty" -#: category/views.py:102 +#: category/views.py:110 #, python-format msgid "Songbook %(name)s was successfully deleted" msgstr "Zpěvník %(name)s byla úspěšně smazána" - diff --git a/category/menus.py b/category/menus.py index 3774141..276bf59 100644 --- a/category/menus.py +++ b/category/menus.py @@ -10,13 +10,23 @@ def categories(): """Returns MenuItems for all Categories""" - return [MenuItem(category["name"], reverse("category:index", - kwargs={"slug": category["slug"]}), skip_translate=True) - for category in Category.objects.values("name", "slug")] + return [ + MenuItem( + category["name"], + reverse("category:index", kwargs={"slug": category["slug"]}), + skip_translate=True, + ) + for category in Category.objects.values("name", "slug") + ] -Menu.add_item("songbook", CacheMenuItem(title=_("Songbooks"), - url=reverse("backend:index"), - generate_function=categories, - key=settings.CATEGORY_CACHE_KEY, - timeout=60 * 60 * 24 * 7)) +Menu.add_item( + "songbook", + CacheMenuItem( + title=_("Songbooks"), + url=reverse("backend:index"), + generate_function=categories, + key=settings.CATEGORY_CACHE_KEY, + timeout=60 * 60 * 24 * 7, + ), +) diff --git a/category/migrations/0001_initial.py b/category/migrations/0001_initial.py index ecea85f..2b2b7da 100644 --- a/category/migrations/0001_initial.py +++ b/category/migrations/0001_initial.py @@ -3,21 +3,48 @@ class Migration(migrations.Migration): - initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Category', + name="Category", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100, unique=True, verbose_name='Category Name')), - ('slug', models.SlugField(max_length=25, unique=True, verbose_name='URL pattern')), - ('generate_pdf', models.BooleanField(help_text='Should the PDF file be generated when something changes?', verbose_name='PDF generation')), - ('locale', models.CharField(choices=[('en', 'English'), ('cs', 'Česky')], default='Czech', help_text='Language for generated PDF files', max_length=5, verbose_name='Language')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField(max_length=100, unique=True, verbose_name="Category Name"), + ), + ( + "slug", + models.SlugField(max_length=25, unique=True, verbose_name="URL pattern"), + ), + ( + "generate_pdf", + models.BooleanField( + help_text="Should the PDF file be generated when something changes?", + verbose_name="PDF generation", + ), + ), + ( + "locale", + models.CharField( + choices=[("en", "English"), ("cs", "Česky")], + default="Czech", + help_text="Language for generated PDF files", + max_length=5, + verbose_name="Language", + ), + ), ], ), ] diff --git a/category/migrations/0002_auto_20200918_1217.py b/category/migrations/0002_auto_20200918_1217.py index 7355e48..67b8fd7 100644 --- a/category/migrations/0002_auto_20200918_1217.py +++ b/category/migrations/0002_auto_20200918_1217.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('category', '0001_initial'), + ("category", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='category', - name='name', - field=models.CharField(max_length=100, unique=True, verbose_name='Name'), + model_name="category", + name="name", + field=models.CharField(max_length=100, unique=True, verbose_name="Name"), ), ] diff --git a/category/migrations/0003_auto_20200925_1902.py b/category/migrations/0003_auto_20200925_1902.py index 6cd9f2b..b9e4afa 100644 --- a/category/migrations/0003_auto_20200925_1902.py +++ b/category/migrations/0003_auto_20200925_1902.py @@ -4,15 +4,17 @@ class Migration(migrations.Migration): - dependencies = [ - ('category', '0002_auto_20200918_1217'), + ("category", "0002_auto_20200918_1217"), ] operations = [ migrations.AlterField( - model_name='category', - name='generate_pdf', - field=models.BooleanField(help_text='Should the PDF file be automatically generated when a song changes?', verbose_name='PDF generation'), + model_name="category", + name="generate_pdf", + field=models.BooleanField( + help_text="Should the PDF file be automatically generated when a song changes?", + verbose_name="PDF generation", + ), ), ] diff --git a/category/migrations/0004_alter_category_options.py b/category/migrations/0004_alter_category_options.py index a22ef9d..a2d4282 100644 --- a/category/migrations/0004_alter_category_options.py +++ b/category/migrations/0004_alter_category_options.py @@ -4,14 +4,13 @@ class Migration(migrations.Migration): - dependencies = [ - ('category', '0003_auto_20200925_1902'), + ("category", "0003_auto_20200925_1902"), ] operations = [ migrations.AlterModelOptions( - name='category', - options={'verbose_name': 'Songbook', 'verbose_name_plural': 'Songbooks'}, + name="category", + options={"verbose_name": "Songbook", "verbose_name_plural": "Songbooks"}, ), ] diff --git a/category/migrations/0005_category_filename_category_image_category_margin_and_more.py b/category/migrations/0005_category_filename_category_image_category_margin_and_more.py index f459dca..331b86f 100644 --- a/category/migrations/0005_category_filename_category_image_category_margin_and_more.py +++ b/category/migrations/0005_category_filename_category_image_category_margin_and_more.py @@ -4,45 +4,77 @@ class Migration(migrations.Migration): - dependencies = [ - ('category', '0004_alter_category_options'), + ("category", "0004_alter_category_options"), ] operations = [ migrations.AddField( - model_name='category', - name='filename', - field=models.CharField(blank=True, help_text='Filename of the generated PDF, please do not include .pdf', max_length=30, verbose_name='File name'), + model_name="category", + name="filename", + field=models.CharField( + blank=True, + help_text="Filename of the generated PDF, please do not include .pdf", + max_length=30, + verbose_name="File name", + ), ), migrations.AddField( - model_name='category', - name='image', - field=models.ImageField(blank=True, help_text='Optional title image of the songbook', null=True, upload_to='uploads/', verbose_name='Title Image'), + model_name="category", + name="image", + field=models.ImageField( + blank=True, + help_text="Optional title image of the songbook", + null=True, + upload_to="uploads/", + verbose_name="Title Image", + ), ), migrations.AddField( - model_name='category', - name='margin', - field=models.FloatField(default=0, help_text='Margins for title image, might be needed for some printers', verbose_name='Title Image margins'), + model_name="category", + name="margin", + field=models.FloatField( + default=0, + help_text="Margins for title image, might be needed for some printers", + verbose_name="Title Image margins", + ), ), migrations.AddField( - model_name='category', - name='show_date', - field=models.BooleanField(default=True, help_text='True, if the date should be included in the final PDF', verbose_name='Show date'), + model_name="category", + name="show_date", + field=models.BooleanField( + default=True, + help_text="True, if the date should be included in the final PDF", + verbose_name="Show date", + ), ), migrations.AddField( - model_name='category', - name='show_title', - field=models.BooleanField(default=True, help_text='True, if the title should be shown on the first page', verbose_name='Show title'), + model_name="category", + name="show_title", + field=models.BooleanField( + default=True, + help_text="True, if the title should be shown on the first page", + verbose_name="Show title", + ), ), migrations.AddField( - model_name='category', - name='title', - field=models.CharField(blank=True, help_text='Name to be used on the title page of the PDF', max_length=100, verbose_name='Title'), + model_name="category", + name="title", + field=models.CharField( + blank=True, + help_text="Name to be used on the title page of the PDF", + max_length=100, + verbose_name="Title", + ), ), migrations.AlterField( - model_name='category', - name='locale', - field=models.CharField(choices=[('en', 'English'), ('cs', 'Česky')], help_text='Language to be used in the generated PDF', max_length=5, verbose_name='Language'), + model_name="category", + name="locale", + field=models.CharField( + choices=[("en", "English"), ("cs", "Česky")], + help_text="Language to be used in the generated PDF", + max_length=5, + verbose_name="Language", + ), ), ] diff --git a/category/migrations/0006_category_link.py b/category/migrations/0006_category_link.py index 1d5c7dc..a7fb2bc 100644 --- a/category/migrations/0006_category_link.py +++ b/category/migrations/0006_category_link.py @@ -4,15 +4,20 @@ class Migration(migrations.Migration): - dependencies = [ - ('category', '0005_category_filename_category_image_category_margin_and_more'), + ("category", "0005_category_filename_category_image_category_margin_and_more"), ] operations = [ migrations.AddField( - model_name='category', - name='link', - field=models.CharField(blank=True, default='', help_text='Link to include in the PDF', max_length=300, verbose_name='Link'), + model_name="category", + name="link", + field=models.CharField( + blank=True, + default="", + help_text="Link to include in the PDF", + max_length=300, + verbose_name="Link", + ), ), ] diff --git a/category/migrations/0007_remove_category_show_title.py b/category/migrations/0007_remove_category_show_title.py index dcacf31..4d1148b 100644 --- a/category/migrations/0007_remove_category_show_title.py +++ b/category/migrations/0007_remove_category_show_title.py @@ -4,14 +4,13 @@ class Migration(migrations.Migration): - dependencies = [ - ('category', '0006_category_link'), + ("category", "0006_category_link"), ] operations = [ migrations.RemoveField( - model_name='category', - name='show_title', + model_name="category", + name="show_title", ), ] diff --git a/category/migrations/0008_category_public.py b/category/migrations/0008_category_public.py index f768300..c67e285 100644 --- a/category/migrations/0008_category_public.py +++ b/category/migrations/0008_category_public.py @@ -4,15 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('category', '0007_remove_category_show_title'), + ("category", "0007_remove_category_show_title"), ] operations = [ migrations.AddField( - model_name='category', - name='public', - field=models.BooleanField(default=True, help_text='True, if the file should be public', verbose_name='Public file'), + model_name="category", + name="public", + field=models.BooleanField( + default=True, + help_text="True, if the file should be public", + verbose_name="Public file", + ), ), ] diff --git a/category/models.py b/category/models.py index 60fe204..07c7c01 100644 --- a/category/models.py +++ b/category/models.py @@ -7,11 +7,13 @@ class Category(PDFOptions): """Represents set of songs""" - name = CharField(verbose_name=_('Name'), max_length=100, unique=True) - slug = SlugField(verbose_name=_('URL pattern'), max_length=25, unique=True) - generate_pdf = BooleanField(verbose_name=_('PDF generation'), - help_text=_('Should the PDF file be automatically generated when a song ' - 'changes?')) + + name = CharField(verbose_name=_("Name"), max_length=100, unique=True) + slug = SlugField(verbose_name=_("URL pattern"), max_length=25, unique=True) + generate_pdf = BooleanField( + verbose_name=_("PDF generation"), + help_text=_("Should the PDF file be automatically generated when a song changes?"), + ) def __str__(self): return self.name diff --git a/category/urls.py b/category/urls.py index 8a430c9..c920f15 100644 --- a/category/urls.py +++ b/category/urls.py @@ -1,14 +1,20 @@ """Backend app URL Configuration""" from django.urls import path -from category.views import CategorySongsListView, CategoryListView, CategoryCreateView, CategoryUpdateView, \ - CategoryDeleteView, CategoryRegeneratePDFView +from category.views import ( + CategorySongsListView, + CategoryListView, + CategoryCreateView, + CategoryUpdateView, + CategoryDeleteView, + CategoryRegeneratePDFView, +) urlpatterns = [ - path('', CategoryListView.as_view(), name="list"), - path('add', CategoryCreateView.as_view(), name="add"), - path('edit/', CategoryUpdateView.as_view(), name="edit"), - path('delete/', CategoryDeleteView.as_view(), name="delete"), - path('regenerate/', CategoryRegeneratePDFView.as_view(), name="regenerate"), - path('', CategorySongsListView.as_view(), name="index"), + path("", CategoryListView.as_view(), name="list"), + path("add", CategoryCreateView.as_view(), name="add"), + path("edit/", CategoryUpdateView.as_view(), name="edit"), + path("delete/", CategoryDeleteView.as_view(), name="delete"), + path("regenerate/", CategoryRegeneratePDFView.as_view(), name="regenerate"), + path("", CategorySongsListView.as_view(), name="index"), ] diff --git a/category/views.py b/category/views.py index c7ade55..c967670 100644 --- a/category/views.py +++ b/category/views.py @@ -25,36 +25,38 @@ class CategorySongsListView(SongListView, AnalyticsMixin): """Shows all songs in a category""" def get_key(self): - return self.kwargs['slug'] + return self.kwargs["slug"] def get_queryset(self): - slug = self.kwargs['slug'] + slug = self.kwargs["slug"] if not Category.objects.filter(slug=slug).exists(): - raise Http404(_("Songbook on url /%(slug)s does not exists") % {'slug': slug}) - return super().get_queryset() \ - .filter(categories__slug=slug) + raise Http404(_("Songbook on url /%(slug)s does not exists") % {"slug": slug}) + return super().get_queryset().filter(categories__slug=slug) -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class CategoryListView(ListView): """Lists all categories""" + model = Category template_name = "category/list.html" context_object_name = "categories" def get_context_data(self, *, object_list=None, **kwargs): ctx = super().get_context_data(object_list=object_list, **kwargs) - ctx["already_staged"] = PDFRequest.objects.filter(type=RequestType.EVENT, - status=Status.QUEUED).values_list("category_id", flat=True) + ctx["already_staged"] = PDFRequest.objects.filter(type=RequestType.EVENT, status=Status.QUEUED).values_list( + "category_id", flat=True + ) return ctx -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class CategoryCreateView(SuccessMessageMixin, CreateView): """Create new category""" + form_class = CategoryForm model = Category - template_name = 'category/add.html' + template_name = "category/add.html" success_url = reverse_lazy("category:list") success_message = _("Songbook %(name)s was successfully created") @@ -63,12 +65,13 @@ def get_success_message(self, cleaned_data): return super().get_success_message(cleaned_data) -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class CategoryUpdateView(SuccessMessageMixin, RegenerateViewMixin, UpdateView): """Updates category""" + form_class = CategoryForm model = Category - template_name = 'category/add.html' + template_name = "category/add.html" success_url = reverse_lazy("category:list") success_message = _("Songbook %(name)s was successfully updated") @@ -80,22 +83,27 @@ def post(self, request, *args, **kwargs): return response -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class CategoryRegeneratePDFView(View, SingleObjectMixin): - """Creates PDF regeneration request for Category, if it doesn't already exist """ + """Creates PDF regeneration request for Category, if it doesn't already exist""" + model = Category def get(self, request, *args, **kwargs): """GET Request""" category = self.get_object() request_pdf_regeneration(category) - messages.success(request, _("Category %s was successfully staged for PDF generation") % category.name) + messages.success( + request, + _("Category %s was successfully staged for PDF generation") % category.name, + ) return redirect("category:list") -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class CategoryDeleteView(DeleteView): """Removes category""" + model = Category template_name = "category/confirm_delete.html" success_url = reverse_lazy("category:list") diff --git a/chords/locale/cs/LC_MESSAGES/django.po b/chords/locale/cs/LC_MESSAGES/django.po index d9c7d4c..6337370 100644 --- a/chords/locale/cs/LC_MESSAGES/django.po +++ b/chords/locale/cs/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-09-26 12:40+0000\n" +"POT-Creation-Date: 2023-09-09 19:21+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -19,6 +19,6 @@ msgstr "" "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n " "<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" -#: chords/settings/base.py:205 +#: chords/settings/base.py:203 msgid "Jerry's songs" msgstr "Jerryho zpěvníček" diff --git a/chords/markdown/chords.py b/chords/markdown/chords.py index a2eccc7..747d5ca 100644 --- a/chords/markdown/chords.py +++ b/chords/markdown/chords.py @@ -2,11 +2,12 @@ from markdown.extensions import Extension from markdown.inlinepatterns import SimpleTagInlineProcessor -CHORD_RE = r'({)(.*?)}' +CHORD_RE = r"({)(.*?)}" class ChordsExtension(Extension): """Markdown extension for convert {chord} into chords""" + def extendMarkdown(self, md): # Insert del pattern into markdown parser md.inlinePatterns.register(ChordPattern(CHORD_RE), "chord", 175) @@ -14,6 +15,7 @@ def extendMarkdown(self, md): class ChordPattern(SimpleTagInlineProcessor): """Pattern for ChordsExtension""" + def __init__(self, pattern): super().__init__(pattern, "span") diff --git a/chords/markdown/chords_pdf.py b/chords/markdown/chords_pdf.py index e7d5eac..0a6fb11 100644 --- a/chords/markdown/chords_pdf.py +++ b/chords/markdown/chords_pdf.py @@ -2,11 +2,12 @@ from markdown.extensions import Extension from markdown.inlinepatterns import SimpleTagInlineProcessor -CHORD_RE = r'({)(.*?)}' +CHORD_RE = r"({)(.*?)}" class ChordsPDFExtension(Extension): """Extension for Chords in PDF""" + def extendMarkdown(self, md): """Chords markdown extension for usage in PDF files""" # Insert del pattern into markdown parser @@ -15,6 +16,7 @@ def extendMarkdown(self, md): class ChordPDFPattern(SimpleTagInlineProcessor): """Pattern for ChordsPDFExtension""" + def __init__(self, pattern): super().__init__(pattern, "sup") diff --git a/chords/markdown/spaces.py b/chords/markdown/spaces.py index 2f0af0d..ead7034 100644 --- a/chords/markdown/spaces.py +++ b/chords/markdown/spaces.py @@ -2,17 +2,19 @@ from markdown.extensions import Extension from markdown.inlinepatterns import SimpleTagInlineProcessor -SPACES_RE = r'(\/)(\d+)\/' +SPACES_RE = r"(\/)(\d+)\/" class SpacesExtension(Extension): """Markdown extension that transforms /{number}/ into actual spaces""" + def extendMarkdown(self, md): md.inlinePatterns.register(SpacesPattern(SPACES_RE), "spaces", 200) class SpacesPattern(SimpleTagInlineProcessor): """Pattern for SpacesExtension""" + def __init__(self, pattern): super().__init__(pattern, "span") diff --git a/chords/settings/base.py b/chords/settings/base.py index 610bc26..e642e55 100644 --- a/chords/settings/base.py +++ b/chords/settings/base.py @@ -30,96 +30,93 @@ # Application definition INSTALLED_APPS = [ - 'frontend', - 'category', - 'backend', - 'pdf', - 'analytics', - - 'bootstrap4', - 'sass_processor', - 'markdownx', - 'menu', - - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - - 'debug_toolbar', - 'django_rq' + "frontend", + "category", + "backend", + "pdf", + "analytics", + "bootstrap4", + "sass_processor", + "markdownx", + "menu", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "debug_toolbar", + "django_rq", ] MARKDOWNX_MARKDOWN_EXTENSIONS = [ - 'markdown.extensions.nl2br', - 'markdown3_newtab', + "markdown.extensions.nl2br", + "markdown3_newtab", ChordsExtension(), - SpacesExtension() + SpacesExtension(), ] MARKDOWNX_PDF_MARKDOWN_EXTENSIONS = [ - 'markdown.extensions.nl2br', - 'markdown3_newtab', + "markdown.extensions.nl2br", + "markdown3_newtab", ChordsPDFExtension(), - SpacesExtension() + SpacesExtension(), ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', + "django.middleware.security.SecurityMiddleware", # 'django.middleware.cache.UpdateCacheMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', + "django.middleware.locale.LocaleMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", # 'django.middleware.cache.FetchFromCacheMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'backend.middleware.settings.SiteNameMiddleware', - 'debug_toolbar.middleware.DebugToolbarMiddleware', + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "backend.middleware.settings.SiteNameMiddleware", + "debug_toolbar.middleware.DebugToolbarMiddleware", ] -ROOT_URLCONF = 'chords.urls' +ROOT_URLCONF = "chords.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] STATICFILES_FINDERS = [ - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - 'sass_processor.finders.CssFinder' + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", + "sass_processor.finders.CssFinder", ] COMPRESS_ENABLED = True SASS_PRECISION = 8 -SASS_OUTPUT_STYLE = 'compact' +SASS_OUTPUT_STYLE = "compact" STATIC_ROOT = "chords/static/" -WSGI_APPLICATION = 'chords.wsgi.application' +WSGI_APPLICATION = "chords.wsgi.application" # Database # https://docs.djangoproject.com/en/2.2/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, '../db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "../db.sqlite3"), } } @@ -129,16 +126,16 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -146,14 +143,14 @@ # Internationalization # https://docs.djangoproject.com/en/2.2/topics/i18n/ -LANGUAGE_CODE = 'cs' +LANGUAGE_CODE = "cs" LANGUAGES = ( - ('en', 'English'), - ('cs', 'Česky'), + ("en", "English"), + ("cs", "Česky"), ) -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -162,19 +159,19 @@ USE_TZ = True CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'LOCATION': 'chords', + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "chords", } } # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.2/howto/static-files/ -STATIC_URL = '/static/' +STATIC_URL = "/static/" -LOGOUT_REDIRECT_URL = '/' -LOGIN_REDIRECT_URL = '/' +LOGOUT_REDIRECT_URL = "/" +LOGIN_REDIRECT_URL = "/" SESSION_COOKIE_AGE = 86400 @@ -182,29 +179,29 @@ MEDIA_URL = "/media/" LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "console": { + "class": "logging.StreamHandler", }, }, - 'root': { - 'handlers': ['console'], - 'level': 'WARNING', + "root": { + "handlers": ["console"], + "level": "WARNING", }, - 'loggers': { - 'django': { - 'handlers': ['console'], - 'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'), - 'propagate': False, + "loggers": { + "django": { + "handlers": ["console"], + "level": os.getenv("DJANGO_LOG_LEVEL", "INFO"), + "propagate": False, }, }, } # Custom settings SITE_NAME = gettext_noop("Jerry's songs") -PDF_FILE_DIR = 'pdfs' +PDF_FILE_DIR = "pdfs" CACHE_TIMEOUT = 86400 # Slug for category to be used on index page, if the category is not found it will return category with lowest id @@ -216,7 +213,7 @@ # might incur performance penalties on production, for production deployment use USE_DYNAMIC_PRERENDER = False -DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" # Cache keys PDF_CACHE_KEY = "PDFS" @@ -224,7 +221,7 @@ PDF_INCLUDE_LINK = "" RQ_QUEUES = { - 'default': { - 'USE_REDIS_CACHE': 'default', + "default": { + "USE_REDIS_CACHE": "default", }, } diff --git a/chords/settings/development.py b/chords/settings/development.py index 3df46f9..a8dfd0a 100644 --- a/chords/settings/development.py +++ b/chords/settings/development.py @@ -1,9 +1,20 @@ """Settings for development""" # pylint: disable=unused-wildcard-import,wildcard-import from .base import * + ALLOWED_HOSTS = ["0.0.0.0"] # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '$sr-v9zx(s!!q)6*2!1#t_+-z5ku*$+=edf*gstxjwz3opj94n' +SECRET_KEY = "$sr-v9zx(s!!q)6*2!1#t_+-z5ku*$+=edf*gstxjwz3opj94n" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True + +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://localhost:6379?db=2", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, + } +} diff --git a/chords/urls.py b/chords/urls.py index faea23a..274d239 100644 --- a/chords/urls.py +++ b/chords/urls.py @@ -19,12 +19,12 @@ from django.urls import path, include, re_path urlpatterns = [ - path('accounts/', include('django.contrib.auth.urls')), - re_path(r'^markdownx/', include('markdownx.urls')), - path('i18n/', include('django.conf.urls.i18n')), - path('pdf/', include(('pdf.urls', "pdf"), namespace="pdf")), - path('categories/', include(('category.urls', "category"), namespace="category")), - path('analytics/', include(('analytics.urls', "analytics"), namespace="analytics")), - re_path(r'^', include(('backend.urls', "backend"), namespace="chords")), - path('__debug__/', include(debug_toolbar.urls)), + path("accounts/", include("django.contrib.auth.urls")), + re_path(r"^markdownx/", include("markdownx.urls")), + path("i18n/", include("django.conf.urls.i18n")), + path("pdf/", include(("pdf.urls", "pdf"), namespace="pdf")), + path("categories/", include(("category.urls", "category"), namespace="category")), + path("analytics/", include(("analytics.urls", "analytics"), namespace="analytics")), + re_path(r"^", include(("backend.urls", "backend"), namespace="chords")), + path("__debug__/", include(debug_toolbar.urls)), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/chords/wsgi.py b/chords/wsgi.py index 6bb0b26..0fd076b 100644 --- a/chords/wsgi.py +++ b/chords/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chords.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chords.settings") application = get_wsgi_application() diff --git a/frontend/apps.py b/frontend/apps.py index b47cf03..7dea73d 100644 --- a/frontend/apps.py +++ b/frontend/apps.py @@ -4,4 +4,5 @@ class FrontendConfig(AppConfig): """Frontend AppConfig""" - name = 'frontend' + + name = "frontend" diff --git a/frontend/locale/cs/LC_MESSAGES/django.po b/frontend/locale/cs/LC_MESSAGES/django.po index ff3926d..2f4fe33 100644 --- a/frontend/locale/cs/LC_MESSAGES/django.po +++ b/frontend/locale/cs/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-06-12 07:19+0000\n" +"POT-Creation-Date: 2023-09-09 19:21+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -80,6 +80,10 @@ msgstr "Jerryho zpěvníček" msgid "Options" msgstr "Možnosti" +#: frontend/templates/base/index.html:69 +msgid "Language" +msgstr "" + #: frontend/templates/base/index.html:89 msgid "Logged as" msgstr "Přihlášen jako" diff --git a/manage.py b/manage.py index 9dd8a6d..ba2896b 100755 --- a/manage.py +++ b/manage.py @@ -5,7 +5,7 @@ def main(): - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chords.settings.development') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chords.settings.development") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -17,5 +17,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/pdf/apps.py b/pdf/apps.py index cb2dfd6..64f62a2 100644 --- a/pdf/apps.py +++ b/pdf/apps.py @@ -4,4 +4,5 @@ class PdfConfig(AppConfig): """PDF app config""" - name = 'pdf' + + name = "pdf" diff --git a/pdf/cachemenuitem.py b/pdf/cachemenuitem.py index 7f2c069..07fb659 100644 --- a/pdf/cachemenuitem.py +++ b/pdf/cachemenuitem.py @@ -5,6 +5,7 @@ class CacheMenuItem(MenuItem): """MenuItem that has dynamic children stored in cache""" + def __init__(self, generate_function, key, timeout, **kwargs): self.generate_function = generate_function self.key = key diff --git a/pdf/forms.py b/pdf/forms.py index d2efa24..4beff60 100644 --- a/pdf/forms.py +++ b/pdf/forms.py @@ -8,23 +8,26 @@ class RequestForm(ModelForm): """Slimmed down model form for PDFRequest""" + class Meta: model = PDFRequest - fields = ['title', 'filename', 'locale', 'show_date', 'image', 'margin'] + fields = ["title", "filename", "locale", "show_date", "image", "margin"] class PDFSongForm(ModelForm): """Slimmed down model form for PDFSong""" + name = CharField(disabled=True, required=False) class Meta: model = PDFSong - fields = ['song_number', 'song'] - widgets = {'song': HiddenInput()} + fields = ["song_number", "song"] + widgets = {"song": HiddenInput()} class BasePDFSongFormset(BaseFormSet): """FormSet for PDFSongForm that checks if song_numbers are unique""" + def clean(self): if any(self.errors): return diff --git a/pdf/generate.py b/pdf/generate.py index 4a3df26..1dfec8f 100644 --- a/pdf/generate.py +++ b/pdf/generate.py @@ -37,10 +37,7 @@ def get_base_url(): Determine base URL to fetch CSS files from `WEASYPRINT_BASEURL` or fall back to using the root path of the URL used in the request. """ - return getattr( - settings, 'WEASYPRINT_BASEURL', - reverse("chords:index") - ) + return getattr(settings, "WEASYPRINT_BASEURL", reverse("chords:index")) def generate_pdf(request: PDFRequest): @@ -59,21 +56,24 @@ def generate_pdf(request: PDFRequest): name = os.path.basename(rel_path) logger.info("Generating %s", name) logger.debug("from request %s", request) - string = render_to_string(template_name="pdf/index.html", context={ - "songs": songs, - "sorted_songs": sorted_songs, - "name": request.title or translation.gettext(settings.SITE_NAME), - "request": request, - "link": request.link - }) + string = render_to_string( + template_name="pdf/index.html", + context={ + "songs": songs, + "sorted_songs": sorted_songs, + "name": request.title or translation.gettext(settings.SITE_NAME), + "request": request, + "link": request.link, + }, + ) PROGRESS_LOGGER.setLevel(logging.INFO) log_filter = ProgressFilter(request) PROGRESS_LOGGER.addFilter(log_filter) weasyprint.HTML( string=string, url_fetcher=django_url_fetcher, - base_url=get_base_url() - ).write_pdf(file, optimize_size=('fonts', 'images')) + base_url=get_base_url(), + ).write_pdf(file, optimize_size=("fonts", "images")) PROGRESS_LOGGER.removeFilter(log_filter) request.file.save(rel_path, File(file, name=rel_path)) request.time_elapsed = ceil(timer.duration) @@ -95,19 +95,15 @@ def generate_pdf_job(request: PDFRequest): def schedule_generation(request: PDFRequest, schedule_time: datetime): """Schedules generation of a request at a specific time""" - queue = get_queue('default') - created_job = queue.enqueue_at( - schedule_time, - generate_pdf, - request, - retry = Retry(max=5, interval=120) - ) + queue = get_queue("default") + created_job = queue.enqueue_at(schedule_time, generate_pdf, request, retry=Retry(max=5, interval=120)) logger.info("Schedule PDF generation of request %s at %s", request.id, schedule_time) return created_job class ProgressFilter(logging.Filter): """Filters Weasyprint progress messages, highly dependent on implementation!""" + STEP_NUMBER = re.compile(r"(?<=Step\s)\d") def __init__(self, request): @@ -124,6 +120,7 @@ def filter(self, record): class Timer: """Context manager for measuring time""" + def __init__(self): self.duration = 0 self.start = 0 diff --git a/pdf/locale/cs/LC_MESSAGES/django.po b/pdf/locale/cs/LC_MESSAGES/django.po index 0b81de1..9f93638 100644 --- a/pdf/locale/cs/LC_MESSAGES/django.po +++ b/pdf/locale/cs/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-09-29 20:17+0000\n" +"POT-Creation-Date: 2023-09-09 19:21+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,13 +18,7 @@ msgstr "" "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n " "<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" -#: pdf/forms.py:18 pdf/models/__init__.py:19 -#: pdf/templates/pdf/requests/list.html:24 -#: pdf/templates/pdf/requests/wait.html:77 -msgid "Title" -msgstr "Titulek" - -#: pdf/forms.py:35 +#: pdf/forms.py:38 msgid "Each song has to have distinct song number" msgstr "Každá písnička musí mít unikátní číslo" @@ -32,114 +26,120 @@ msgstr "Každá písnička musí mít unikátní číslo" msgid "Create PDF" msgstr "Vytvořit PDF" -#: pdf/menus.py:30 pdf/templates/pdf/requests/list.html:5 +#: pdf/menus.py:29 pdf/templates/pdf/requests/list.html:5 #: pdf/templates/pdf/requests/list.html:6 #: pdf/templates/pdf/requests/select.html:6 #: pdf/templates/pdf/requests/select.html:7 msgid "PDF Requests" msgstr "PDF požadavky" -#: pdf/menus.py:34 +#: pdf/menus.py:35 msgid "PDF" msgstr "" -#: pdf/menus.py:40 +#: pdf/menus.py:46 msgid "Files" msgstr "Soubory" -#: pdf/models/__init__.py:11 +#: pdf/models/__init__.py:13 msgid "Filename of the generated PDF, please do not include .pdf" msgstr "Název vygenerovaného PDF, prosím piště bez přípony .pdf" -#: pdf/models/__init__.py:12 +#: pdf/models/__init__.py:14 msgid "File name" msgstr "Název souboru" -#: pdf/models/__init__.py:14 +#: pdf/models/__init__.py:18 msgid "True, if the file should be public" msgstr "Zaškrkněte, pokud chcete aby byl PDF soubor veřejně viditelný" -#: pdf/models/__init__.py:15 +#: pdf/models/__init__.py:19 msgid "Public file" msgstr "Veřejný soubor" -#: pdf/models/__init__.py:16 +#: pdf/models/__init__.py:23 msgid "Language" msgstr "Jazyk" -#: pdf/models/__init__.py:17 +#: pdf/models/__init__.py:25 msgid "Language to be used in the generated PDF" msgstr "Jazyk pro použití ve vygenerovaných PDF" -#: pdf/models/__init__.py:21 +#: pdf/models/__init__.py:30 msgid "Name to be used on the title page of the PDF" msgstr "Jméno pro titulní stranu pdf souboru" -#: pdf/models/__init__.py:22 pdf/templates/pdf/requests/list.html:24 +#: pdf/models/__init__.py:31 pdf/templates/pdf/requests/list.html:34 #: pdf/templates/pdf/requests/wait.html:74 msgid "Title" -msgstr "Titulek" +msgstr "Název" -#: pdf/models/__init__.py:23 +#: pdf/models/__init__.py:35 msgid "Show date" msgstr "Vložit datum" -#: pdf/models/__init__.py:24 +#: pdf/models/__init__.py:36 msgid "True, if the date should be included in the final PDF" msgstr "Zaškrkněte, pokud chcete aby v PDF souboru bylo datum" -#: pdf/models/__init__.py:25 +#: pdf/models/__init__.py:39 msgid "Title Image" msgstr "Úvodní obrázek" -#: pdf/models/__init__.py:26 +#: pdf/models/__init__.py:40 msgid "Optional title image of the songbook" msgstr "Volitelný obrázek na úvodní stránku " -#: pdf/models/__init__.py:30 +#: pdf/models/__init__.py:46 msgid "Title Image margins" msgstr "Odsazení úvodního obrázku" -#: pdf/models/__init__.py:31 +#: pdf/models/__init__.py:47 msgid "Margins for title image, might be needed for some printers" msgstr "" "Odsazení obrázku na titulní stránce, může být nutné pro některé tiskárny" -#: pdf/models/__init__.py:35 +#: pdf/models/__init__.py:53 msgid "Link to include in the PDF" msgstr "Odkaz, který bude v PDF" -#: pdf/models/__init__.py:36 pdf/templates/pdf/requests/list.html:30 +#: pdf/models/__init__.py:54 pdf/templates/pdf/requests/list.html:38 #: pdf/templates/pdf/requests/wait.html:91 msgid "Link" msgstr "Odkaz" -#: pdf/models/request.py:19 +#: pdf/models/request.py:35 pdf/templates/pdf/requests/list.html:20 msgid "Automated" msgstr "Automaticky" -#: pdf/models/request.py:20 +#: pdf/models/request.py:36 pdf/templates/pdf/requests/list.html:16 msgid "Manual" msgstr "Manuální" -#: pdf/models/request.py:25 +#: pdf/models/request.py:42 msgid "Queued" msgstr "Ve frontě" -#: pdf/models/request.py:26 +#: pdf/models/request.py:43 +msgid "Scheduled" +msgstr "" + +#: pdf/models/request.py:44 msgid "In progress" msgstr "Generuje se" -#: pdf/models/request.py:27 +#: pdf/models/request.py:45 msgid "Done" msgstr "Hotovo" -#: pdf/models/request.py:28 +#: pdf/models/request.py:46 msgid "Failed" msgstr "Selhalo" -#: pdf/models/request.py:74 -msgid "Song numbers" +#: pdf/models/request.py:93 +#, fuzzy +#| msgid "Song numbers" +msgid "Song number" msgstr "Čísla písniček" #: pdf/templates/pdf/index.html:46 @@ -168,46 +168,42 @@ msgstr "Seřadit podle jména" msgid "Finish" msgstr "Dokončit" -#: pdf/templates/pdf/requests/list.html:16 +#: pdf/templates/pdf/requests/list.html:26 #: pdf/templates/pdf/requests/select.html:18 msgid "Search" msgstr "Hledat" -#: pdf/templates/pdf/requests/list.html:18 +#: pdf/templates/pdf/requests/list.html:28 #: pdf/templates/pdf/requests/select.html:20 msgid "Insert query" msgstr "Vložte text" -#: pdf/templates/pdf/requests/list.html:26 -msgid "Date Created" -msgstr "Datum vytvoření" - -#: pdf/templates/pdf/requests/list.html:27 +#: pdf/templates/pdf/requests/list.html:35 msgid "Last updated" msgstr "Naposledy změněno" -#: pdf/templates/pdf/requests/list.html:28 +#: pdf/templates/pdf/requests/list.html:36 msgid "Type" msgstr "Typ" -#: pdf/templates/pdf/requests/list.html:29 +#: pdf/templates/pdf/requests/list.html:37 #: pdf/templates/pdf/requests/wait.html:87 msgid "Status" msgstr "Status" -#: pdf/templates/pdf/requests/list.html:31 +#: pdf/templates/pdf/requests/list.html:39 msgid "Time elapsed" msgstr "Trvání" -#: pdf/templates/pdf/requests/list.html:32 +#: pdf/templates/pdf/requests/list.html:40 msgid "Category" msgstr "Kategorie" -#: pdf/templates/pdf/requests/list.html:33 +#: pdf/templates/pdf/requests/list.html:41 msgid "Delete file" msgstr "Odstranit soubor" -#: pdf/templates/pdf/requests/list.html:34 +#: pdf/templates/pdf/requests/list.html:42 #: pdf/templates/pdf/requests/list.html:76 msgid "Regenerate" msgstr "Přegenerovat" @@ -291,39 +287,43 @@ msgstr "Bez nadpisu" msgid "Progress" msgstr "Postup" -#: pdf/utils.py:73 +#: pdf/utils.py:81 msgid "songbook" msgstr "zpevnik" -#: pdf/views.py:41 +#: pdf/views.py:43 #, python-format msgid "Request %(id)s is already in queue" msgstr "Požadavek %(id)s je již ve frontě" -#: pdf/views.py:46 -#, python-format -msgid "Request %(id)s was marked for regeneration" +#: pdf/views.py:49 +#, fuzzy, python-format +#| msgid "Request %(id)s was marked for regeneration" +msgid "Request %(id)s was scheduled for regeneration" msgstr "Požadavek %(id)s byl označen k přegenerování" -#: pdf/views.py:60 +#: pdf/views.py:66 #, python-format msgid "Unable to remove file from request %(id)s that doesn't have one" msgstr "Nepodařilo se odstranit soubor z požadavku %(id)s, protože žádný nemá" -#: pdf/views.py:67 +#: pdf/views.py:73 #, python-format msgid "File %(name)s was successfully deleted" msgstr "Soubor %(name)s byl úspěšně odstraněn" -#: pdf/views.py:105 +#: pdf/views.py:109 msgid "Required parameters not found" msgstr "Požadované parametry nenalezeny" -#: pdf/views.py:110 +#: pdf/views.py:114 msgid "You need to select at least one song" msgstr "Musíte vybrat alespoň jednu písničku" -#: pdf/views.py:137 +#: pdf/views.py:138 #, python-format msgid "PDF Request with id %(id)s was successfully created" msgstr "PDF požadavek se id %(id)s byl úspěšně vytvořen" + +#~ msgid "Date Created" +#~ msgstr "Datum vytvoření" diff --git a/pdf/locales.py b/pdf/locales.py index 8108c6a..951250c 100644 --- a/pdf/locales.py +++ b/pdf/locales.py @@ -3,8 +3,8 @@ from contextlib import contextmanager LANG_TO_LOCALE = { - 'en': 'en_US.UTF8', - 'cs': 'cs_CZ.UTF8', + "en": "en_US.UTF8", + "cs": "cs_CZ.UTF8", } diff --git a/pdf/management/commands/generate_pdf.py b/pdf/management/commands/generate_pdf.py index 387c5a0..20291ef 100644 --- a/pdf/management/commands/generate_pdf.py +++ b/pdf/management/commands/generate_pdf.py @@ -19,18 +19,19 @@ class Command(BaseCommand): """Generates PDF according to the PDF requests""" - help = 'Generates PDFs that were requested' + + help = "Generates PDFs that were requested" def add_arguments(self, parser: ArgumentParser): - parser.add_argument('requests', - metavar='Requests', - type=int, - nargs='?', - default="0", - help="Number of requests to process, will process all requests if not value is specified") - parser.add_argument('--all', - action='store_true', - help="Regenerates PDF for all categories") + parser.add_argument( + "requests", + metavar="Requests", + type=int, + nargs="?", + default="0", + help="Number of requests to process, will process all requests if not value is specified", + ) + parser.add_argument("--all", action="store_true", help="Regenerates PDF for all categories") # pylint: disable=too-many-locals def handle(self, *args, **options): diff --git a/pdf/menus.py b/pdf/menus.py index 2f318c0..8082801 100644 --- a/pdf/menus.py +++ b/pdf/menus.py @@ -16,7 +16,7 @@ def distinct_requests(): """ files = set() data = [] - for entry in PDFRequest.objects.filter(file__isnull=False, status=Status.DONE).exclude(file__exact=''): + for entry in PDFRequest.objects.filter(file__isnull=False, status=Status.DONE).exclude(file__exact=""): # pylint: disable=protected-access if entry.filename not in files and entry.public: data.append(MenuItem(entry.filename, entry.file.url)) @@ -25,20 +25,28 @@ def distinct_requests(): pdf_children = ( - MenuItem(_("Create PDF"), - reverse("pdf:new")), - MenuItem(_("PDF Requests"), - reverse("pdf:list")), + MenuItem(_("Create PDF"), reverse("pdf:new")), + MenuItem(_("PDF Requests"), reverse("pdf:list")), ) -Menu.add_item("pdf", MenuItem(_("PDF"), - reverse("backend:index"), - children=pdf_children, - check=lambda request: request.user.is_authenticated)) +Menu.add_item( + "pdf", + MenuItem( + _("PDF"), + reverse("backend:index"), + children=pdf_children, + check=lambda request: request.user.is_authenticated, + ), +) -Menu.add_item("files", CacheMenuItem(title=_("Files"), - url=reverse("backend:index"), - generate_function=distinct_requests, - key=settings.PDF_CACHE_KEY, - timeout=60 * 60)) +Menu.add_item( + "files", + CacheMenuItem( + title=_("Files"), + url=reverse("backend:index"), + generate_function=distinct_requests, + key=settings.PDF_CACHE_KEY, + timeout=60 * 60, + ), +) diff --git a/pdf/migrations/0001_initial.py b/pdf/migrations/0001_initial.py index 50fa898..a77018c 100644 --- a/pdf/migrations/0001_initial.py +++ b/pdf/migrations/0001_initial.py @@ -4,25 +4,58 @@ class Migration(migrations.Migration): - initial = True dependencies = [ - ('backend', '0005_auto_20191124_1327'), + ("backend", "0005_auto_20191124_1327"), ] operations = [ migrations.CreateModel( - name='PDFRequest', + name="PDFRequest", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateField(auto_now_add=True)), - ('update_date', models.DateTimeField(auto_now=True)), - ('locale', models.CharField(choices=[('en', 'English'), ('cs', 'Czech')], max_length=5, verbose_name='Language')), - ('type', models.CharField(choices=[('EV', 'Automated'), ('MA', 'Manual')], default='EV', max_length=2)), - ('status', models.CharField(choices=[('QU', 'Queued'), ('PR', 'In progress'), ('DO', 'Done'), ('ER', 'Failed')], default='QU', max_length=2)), - ('filename', models.CharField(max_length=30, null=True)), - ('songs', models.ManyToManyField(to='backend.Song')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateField(auto_now_add=True)), + ("update_date", models.DateTimeField(auto_now=True)), + ( + "locale", + models.CharField( + choices=[("en", "English"), ("cs", "Czech")], + max_length=5, + verbose_name="Language", + ), + ), + ( + "type", + models.CharField( + choices=[("EV", "Automated"), ("MA", "Manual")], + default="EV", + max_length=2, + ), + ), + ( + "status", + models.CharField( + choices=[ + ("QU", "Queued"), + ("PR", "In progress"), + ("DO", "Done"), + ("ER", "Failed"), + ], + default="QU", + max_length=2, + ), + ), + ("filename", models.CharField(max_length=30, null=True)), + ("songs", models.ManyToManyField(to="backend.Song")), ], ), ] diff --git a/pdf/migrations/0002_pdfrequest_time_elapsed.py b/pdf/migrations/0002_pdfrequest_time_elapsed.py index 696a977..7d3c7db 100644 --- a/pdf/migrations/0002_pdfrequest_time_elapsed.py +++ b/pdf/migrations/0002_pdfrequest_time_elapsed.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0001_initial'), + ("pdf", "0001_initial"), ] operations = [ migrations.AddField( - model_name='pdfrequest', - name='time_elapsed', + model_name="pdfrequest", + name="time_elapsed", field=models.IntegerField(null=True), ), ] diff --git a/pdf/migrations/0003_auto_20200808_1337.py b/pdf/migrations/0003_auto_20200808_1337.py index 94a01e4..85c0beb 100644 --- a/pdf/migrations/0003_auto_20200808_1337.py +++ b/pdf/migrations/0003_auto_20200808_1337.py @@ -4,15 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0002_pdfrequest_time_elapsed'), + ("pdf", "0002_pdfrequest_time_elapsed"), ] operations = [ migrations.AlterField( - model_name='pdfrequest', - name='locale', - field=models.CharField(choices=[('en', 'English'), ('cs', 'Česky')], max_length=5, verbose_name='Language'), + model_name="pdfrequest", + name="locale", + field=models.CharField( + choices=[("en", "English"), ("cs", "Česky")], + max_length=5, + verbose_name="Language", + ), ), ] diff --git a/pdf/migrations/0004_request_20200918_1217.py b/pdf/migrations/0004_request_20200918_1217.py index 0179be1..4bfcc92 100644 --- a/pdf/migrations/0004_request_20200918_1217.py +++ b/pdf/migrations/0004_request_20200918_1217.py @@ -5,8 +5,8 @@ def migrate_requests(apps, schema_editor): - PDFRequest = apps.get_model('pdf', 'PDFRequest') - Category = apps.get_model('category', 'Category') + PDFRequest = apps.get_model("pdf", "PDFRequest") + Category = apps.get_model("category", "Category") locales = {code: Category.objects.get(name=name) for code, name in settings.LANGUAGES} with transaction.atomic(): for request in PDFRequest.objects.all(): @@ -15,66 +15,89 @@ def migrate_requests(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ - ('category', '0002_auto_20200918_1217'), - ('backend', '0007_auto_20200918_1217'), - ('pdf', '0003_auto_20200808_1337'), + ("category", "0002_auto_20200918_1217"), + ("backend", "0007_auto_20200918_1217"), + ("pdf", "0003_auto_20200808_1337"), ] operations = [ migrations.CreateModel( - name='PDFSong', + name="PDFSong", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('song_number', models.IntegerField()), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("song_number", models.IntegerField()), ], ), migrations.AddField( - model_name='pdfrequest', - name='category', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='category.Category'), + model_name="pdfrequest", + name="category", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="category.Category", + ), ), migrations.RunPython(migrate_requests), migrations.AddField( - model_name='pdfrequest', - name='file', - field=models.FileField(null=True, upload_to=''), + model_name="pdfrequest", + name="file", + field=models.FileField(null=True, upload_to=""), ), migrations.AlterField( - model_name='pdfrequest', - name='status', - field=models.CharField(choices=[('QU', 'Queued'), ('PR', 'In progress'), ('DO', 'Done'), ('FA', 'Failed')], default='QU', max_length=2), + model_name="pdfrequest", + name="status", + field=models.CharField( + choices=[ + ("QU", "Queued"), + ("PR", "In progress"), + ("DO", "Done"), + ("FA", "Failed"), + ], + default="QU", + max_length=2, + ), ), migrations.AddConstraint( - model_name='pdfrequest', - constraint=models.CheckConstraint(check=models.Q(('type', 'MA'), ('category__isnull', False), _connector='OR'), name='automated_category_present'), + model_name="pdfrequest", + constraint=models.CheckConstraint( + check=models.Q(("type", "MA"), ("category__isnull", False), _connector="OR"), + name="automated_category_present", + ), ), migrations.AddField( - model_name='pdfsong', - name='request', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pdf.PDFRequest'), + model_name="pdfsong", + name="request", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="pdf.PDFRequest"), ), migrations.AddField( - model_name='pdfsong', - name='song', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.Song'), + model_name="pdfsong", + name="song", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="backend.Song"), ), migrations.RemoveField( - model_name='pdfrequest', - name='songs', + model_name="pdfrequest", + name="songs", ), migrations.AddField( - model_name='pdfrequest', - name='songs', - field=models.ManyToManyField(through='pdf.PDFSong', to='backend.Song'), + model_name="pdfrequest", + name="songs", + field=models.ManyToManyField(through="pdf.PDFSong", to="backend.Song"), ), migrations.AlterUniqueTogether( - name='pdfsong', - unique_together={('song_number', 'request', 'song')}, + name="pdfsong", + unique_together={("song_number", "request", "song")}, ), migrations.AlterModelOptions( - name='pdfrequest', - options={'ordering': ['-update_date']}, + name="pdfrequest", + options={"ordering": ["-update_date"]}, ), ] diff --git a/pdf/migrations/0005_auto_20200919_2130.py b/pdf/migrations/0005_auto_20200919_2130.py index 870ec40..5a3629f 100644 --- a/pdf/migrations/0005_auto_20200919_2130.py +++ b/pdf/migrations/0005_auto_20200919_2130.py @@ -5,15 +5,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0004_request_20200918_1217'), + ("pdf", "0004_request_20200918_1217"), ] operations = [ migrations.AlterField( - model_name='pdfrequest', - name='file', - field=models.FileField(null=True, storage=pdf.storage.DateOverwriteStorage(), upload_to=''), + model_name="pdfrequest", + name="file", + field=models.FileField(null=True, storage=pdf.storage.DateOverwriteStorage(), upload_to=""), ), ] diff --git a/pdf/migrations/0006_auto_20200925_1902.py b/pdf/migrations/0006_auto_20200925_1902.py index 17442ec..d611eb4 100644 --- a/pdf/migrations/0006_auto_20200925_1902.py +++ b/pdf/migrations/0006_auto_20200925_1902.py @@ -5,20 +5,19 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0005_auto_20200919_2130'), + ("pdf", "0005_auto_20200919_2130"), ] operations = [ migrations.AddField( - model_name='pdfrequest', - name='name', + model_name="pdfrequest", + name="name", field=models.CharField(max_length=100, null=True), ), migrations.AlterField( - model_name='pdfsong', - name='song_number', + model_name="pdfsong", + name="song_number", field=models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1)]), ), ] diff --git a/pdf/migrations/0007_auto_20200925_1946.py b/pdf/migrations/0007_auto_20200925_1946.py index 9569f90..e101714 100644 --- a/pdf/migrations/0007_auto_20200925_1946.py +++ b/pdf/migrations/0007_auto_20200925_1946.py @@ -4,25 +4,37 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0006_auto_20200925_1902'), + ("pdf", "0006_auto_20200925_1902"), ] operations = [ migrations.AlterField( - model_name='pdfrequest', - name='filename', - field=models.CharField(help_text='Filename of the generated PDF, please do not include .pdf', max_length=30, null=True), + model_name="pdfrequest", + name="filename", + field=models.CharField( + help_text="Filename of the generated PDF, please do not include .pdf", + max_length=30, + null=True, + ), ), migrations.AlterField( - model_name='pdfrequest', - name='locale', - field=models.CharField(choices=[('en', 'English'), ('cs', 'Česky')], help_text='Language to be used in the generated PDF', max_length=5, verbose_name='Language'), + model_name="pdfrequest", + name="locale", + field=models.CharField( + choices=[("en", "English"), ("cs", "Česky")], + help_text="Language to be used in the generated PDF", + max_length=5, + verbose_name="Language", + ), ), migrations.AlterField( - model_name='pdfrequest', - name='name', - field=models.CharField(help_text='Name to be used on the title page of the PDF', max_length=100, null=True), + model_name="pdfrequest", + name="name", + field=models.CharField( + help_text="Name to be used on the title page of the PDF", + max_length=100, + null=True, + ), ), ] diff --git a/pdf/migrations/0008_auto_20200925_2002.py b/pdf/migrations/0008_auto_20200925_2002.py index c8f21d3..0d8fd76 100644 --- a/pdf/migrations/0008_auto_20200925_2002.py +++ b/pdf/migrations/0008_auto_20200925_2002.py @@ -4,15 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0007_auto_20200925_1946'), + ("pdf", "0007_auto_20200925_1946"), ] operations = [ migrations.AlterField( - model_name='pdfrequest', - name='name', - field=models.CharField(default="Jerry's songs", help_text='Name to be used on the title page of the PDF', max_length=100), + model_name="pdfrequest", + name="name", + field=models.CharField( + default="Jerry's songs", + help_text="Name to be used on the title page of the PDF", + max_length=100, + ), ), ] diff --git a/pdf/migrations/0009_auto_20210805_1842.py b/pdf/migrations/0009_auto_20210805_1842.py index 2e11db0..ff61622 100644 --- a/pdf/migrations/0009_auto_20210805_1842.py +++ b/pdf/migrations/0009_auto_20210805_1842.py @@ -4,30 +4,48 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0008_auto_20200925_2002'), + ("pdf", "0008_auto_20200925_2002"), ] operations = [ migrations.AddField( - model_name='pdfrequest', - name='image', - field=models.ImageField(help_text='Optional title image of the songbook', null=True, upload_to='uploads/', verbose_name='Title Image'), + model_name="pdfrequest", + name="image", + field=models.ImageField( + help_text="Optional title image of the songbook", + null=True, + upload_to="uploads/", + verbose_name="Title Image", + ), ), migrations.AddField( - model_name='pdfrequest', - name='show_date', - field=models.BooleanField(default=True, help_text='True, if the date should be included in the final PDF', verbose_name='Show date'), + model_name="pdfrequest", + name="show_date", + field=models.BooleanField( + default=True, + help_text="True, if the date should be included in the final PDF", + verbose_name="Show date", + ), ), migrations.AlterField( - model_name='pdfrequest', - name='filename', - field=models.CharField(help_text='Filename of the generated PDF, please do not include .pdf', max_length=30, null=True, verbose_name='File name'), + model_name="pdfrequest", + name="filename", + field=models.CharField( + help_text="Filename of the generated PDF, please do not include .pdf", + max_length=30, + null=True, + verbose_name="File name", + ), ), migrations.AlterField( - model_name='pdfrequest', - name='name', - field=models.CharField(default="Jerry's songs", help_text='Name to be used on the title page of the PDF', max_length=100, verbose_name='Name'), + model_name="pdfrequest", + name="name", + field=models.CharField( + default="Jerry's songs", + help_text="Name to be used on the title page of the PDF", + max_length=100, + verbose_name="Name", + ), ), ] diff --git a/pdf/migrations/0010_alter_pdfrequest_image.py b/pdf/migrations/0010_alter_pdfrequest_image.py index 067a307..8eb7c12 100644 --- a/pdf/migrations/0010_alter_pdfrequest_image.py +++ b/pdf/migrations/0010_alter_pdfrequest_image.py @@ -4,15 +4,20 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0009_auto_20210805_1842'), + ("pdf", "0009_auto_20210805_1842"), ] operations = [ migrations.AlterField( - model_name='pdfrequest', - name='image', - field=models.ImageField(blank=True, help_text='Optional title image of the songbook', null=True, upload_to='uploads/', verbose_name='Title Image'), + model_name="pdfrequest", + name="image", + field=models.ImageField( + blank=True, + help_text="Optional title image of the songbook", + null=True, + upload_to="uploads/", + verbose_name="Title Image", + ), ), ] diff --git a/pdf/migrations/0011_alter_pdfsong_song_number.py b/pdf/migrations/0011_alter_pdfsong_song_number.py index a2b5f21..2fc3167 100644 --- a/pdf/migrations/0011_alter_pdfsong_song_number.py +++ b/pdf/migrations/0011_alter_pdfsong_song_number.py @@ -5,15 +5,17 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0010_alter_pdfrequest_image'), + ("pdf", "0010_alter_pdfrequest_image"), ] operations = [ migrations.AlterField( - model_name='pdfsong', - name='song_number', - field=models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1)], verbose_name='Song number'), + model_name="pdfsong", + name="song_number", + field=models.PositiveIntegerField( + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="Song number", + ), ), ] diff --git a/pdf/migrations/0012_pdfrequest_margin.py b/pdf/migrations/0012_pdfrequest_margin.py index 2ca0e96..cc13447 100644 --- a/pdf/migrations/0012_pdfrequest_margin.py +++ b/pdf/migrations/0012_pdfrequest_margin.py @@ -4,15 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0011_alter_pdfsong_song_number'), + ("pdf", "0011_alter_pdfsong_song_number"), ] operations = [ migrations.AddField( - model_name='pdfrequest', - name='margin', - field=models.FloatField(default=0, help_text='Margins for title image, might be needed for some printers', verbose_name='Title Image margins'), + model_name="pdfrequest", + name="margin", + field=models.FloatField( + default=0, + help_text="Margins for title image, might be needed for some printers", + verbose_name="Title Image margins", + ), ), ] diff --git a/pdf/migrations/0013_pdfrequest_show_title.py b/pdf/migrations/0013_pdfrequest_show_title.py index 5a3c20d..ce74e5c 100644 --- a/pdf/migrations/0013_pdfrequest_show_title.py +++ b/pdf/migrations/0013_pdfrequest_show_title.py @@ -4,15 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0012_pdfrequest_margin'), + ("pdf", "0012_pdfrequest_margin"), ] operations = [ migrations.AddField( - model_name='pdfrequest', - name='show_title', - field=models.BooleanField(default=True, help_text='If the title should be shown ', verbose_name='Show title'), + model_name="pdfrequest", + name="show_title", + field=models.BooleanField( + default=True, + help_text="If the title should be shown ", + verbose_name="Show title", + ), ), ] diff --git a/pdf/migrations/0014_auto_20210928_1029.py b/pdf/migrations/0014_auto_20210928_1029.py index a850414..53b074c 100644 --- a/pdf/migrations/0014_auto_20210928_1029.py +++ b/pdf/migrations/0014_auto_20210928_1029.py @@ -4,20 +4,27 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0013_pdfrequest_show_title'), + ("pdf", "0013_pdfrequest_show_title"), ] operations = [ migrations.AlterField( - model_name='pdfrequest', - name='name', - field=models.CharField(help_text='Name to be used on the title page of the PDF', max_length=100, verbose_name='Name'), + model_name="pdfrequest", + name="name", + field=models.CharField( + help_text="Name to be used on the title page of the PDF", + max_length=100, + verbose_name="Name", + ), ), migrations.AlterField( - model_name='pdfrequest', - name='show_title', - field=models.BooleanField(default=True, help_text='True, if the title should be shown on the first page', verbose_name='Show title'), + model_name="pdfrequest", + name="show_title", + field=models.BooleanField( + default=True, + help_text="True, if the title should be shown on the first page", + verbose_name="Show title", + ), ), ] diff --git a/pdf/migrations/0015_remove_pdfrequest_name_pdfrequest_title_and_more.py b/pdf/migrations/0015_remove_pdfrequest_name_pdfrequest_title_and_more.py index 3fdea73..43092ca 100644 --- a/pdf/migrations/0015_remove_pdfrequest_name_pdfrequest_title_and_more.py +++ b/pdf/migrations/0015_remove_pdfrequest_name_pdfrequest_title_and_more.py @@ -4,26 +4,36 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0014_auto_20210928_1029'), + ("pdf", "0014_auto_20210928_1029"), ] operations = [ migrations.RenameField( - model_name='pdfrequest', - old_name='name', - new_name='title', + model_name="pdfrequest", + old_name="name", + new_name="title", ), migrations.AlterField( - model_name='pdfrequest', - name='title', - field=models.CharField(blank=True, help_text='Name to be used on the title page of the PDF', max_length=100, verbose_name='Title'), + model_name="pdfrequest", + name="title", + field=models.CharField( + blank=True, + help_text="Name to be used on the title page of the PDF", + max_length=100, + verbose_name="Title", + ), ), migrations.AlterField( - model_name='pdfrequest', - name='filename', - field=models.CharField(blank=True, default='', help_text='Filename of the generated PDF, please do not include .pdf', max_length=30, verbose_name='File name'), + model_name="pdfrequest", + name="filename", + field=models.CharField( + blank=True, + default="", + help_text="Filename of the generated PDF, please do not include .pdf", + max_length=30, + verbose_name="File name", + ), preserve_default=False, ), ] diff --git a/pdf/migrations/0016_pdfrequest_link.py b/pdf/migrations/0016_pdfrequest_link.py index 4ea4ce1..d4b5282 100644 --- a/pdf/migrations/0016_pdfrequest_link.py +++ b/pdf/migrations/0016_pdfrequest_link.py @@ -4,15 +4,20 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0015_remove_pdfrequest_name_pdfrequest_title_and_more'), + ("pdf", "0015_remove_pdfrequest_name_pdfrequest_title_and_more"), ] operations = [ migrations.AddField( - model_name='pdfrequest', - name='link', - field=models.CharField(blank=True, default='', help_text='Link to include in the PDF', max_length=300, verbose_name='Link'), + model_name="pdfrequest", + name="link", + field=models.CharField( + blank=True, + default="", + help_text="Link to include in the PDF", + max_length=300, + verbose_name="Link", + ), ), ] diff --git a/pdf/migrations/0017_remove_pdfrequest_show_title.py b/pdf/migrations/0017_remove_pdfrequest_show_title.py index e0cb963..f7afa0e 100644 --- a/pdf/migrations/0017_remove_pdfrequest_show_title.py +++ b/pdf/migrations/0017_remove_pdfrequest_show_title.py @@ -4,14 +4,13 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0016_pdfrequest_link'), + ("pdf", "0016_pdfrequest_link"), ] operations = [ migrations.RemoveField( - model_name='pdfrequest', - name='show_title', + model_name="pdfrequest", + name="show_title", ), ] diff --git a/pdf/migrations/0018_pdfrequest_progress.py b/pdf/migrations/0018_pdfrequest_progress.py index 6cb0b12..8512df6 100644 --- a/pdf/migrations/0018_pdfrequest_progress.py +++ b/pdf/migrations/0018_pdfrequest_progress.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0017_remove_pdfrequest_show_title'), + ("pdf", "0017_remove_pdfrequest_show_title"), ] operations = [ migrations.AddField( - model_name='pdfrequest', - name='progress', + model_name="pdfrequest", + name="progress", field=models.IntegerField(default=0), ), ] diff --git a/pdf/migrations/0019_pdfrequest_public.py b/pdf/migrations/0019_pdfrequest_public.py index b4a731c..107f3db 100644 --- a/pdf/migrations/0019_pdfrequest_public.py +++ b/pdf/migrations/0019_pdfrequest_public.py @@ -4,15 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0018_pdfrequest_progress'), + ("pdf", "0018_pdfrequest_progress"), ] operations = [ migrations.AddField( - model_name='pdfrequest', - name='public', - field=models.BooleanField(default=True, help_text='True, if the file should be public', verbose_name='Public file'), + model_name="pdfrequest", + name="public", + field=models.BooleanField( + default=True, + help_text="True, if the file should be public", + verbose_name="Public file", + ), ), ] diff --git a/pdf/migrations/0020_remove_pdfrequest_created_date_and_more.py b/pdf/migrations/0020_remove_pdfrequest_created_date_and_more.py index e45ad9c..381c330 100644 --- a/pdf/migrations/0020_remove_pdfrequest_created_date_and_more.py +++ b/pdf/migrations/0020_remove_pdfrequest_created_date_and_more.py @@ -4,24 +4,33 @@ class Migration(migrations.Migration): - dependencies = [ - ('pdf', '0019_pdfrequest_public'), + ("pdf", "0019_pdfrequest_public"), ] operations = [ migrations.RemoveField( - model_name='pdfrequest', - name='created_date', + model_name="pdfrequest", + name="created_date", ), migrations.AddField( - model_name='pdfrequest', - name='scheduled_at', + model_name="pdfrequest", + name="scheduled_at", field=models.DateTimeField(null=True), ), migrations.AlterField( - model_name='pdfrequest', - name='status', - field=models.CharField(choices=[('QU', 'Queued'), ('SC', 'Scheduled'), ('PR', 'In progress'), ('DO', 'Done'), ('FA', 'Failed')], default='QU', max_length=2), + model_name="pdfrequest", + name="status", + field=models.CharField( + choices=[ + ("QU", "Queued"), + ("SC", "Scheduled"), + ("PR", "In progress"), + ("DO", "Done"), + ("FA", "Failed"), + ], + default="QU", + max_length=2, + ), ), ] diff --git a/pdf/models/__init__.py b/pdf/models/__init__.py index b9060f0..fb8b879 100644 --- a/pdf/models/__init__.py +++ b/pdf/models/__init__.py @@ -6,35 +6,54 @@ class PDFOptions(Model): """All options for PDF Generation in a form of a abstract model""" - filename = CharField(max_length=30, - blank=True, - help_text=_("Filename of the generated PDF, please do not include .pdf"), - verbose_name=_("File name")) - public = BooleanField(default=True, - help_text=_("True, if the file should be public"), - verbose_name=_("Public file")) - locale = CharField(choices=settings.LANGUAGES, verbose_name=_('Language'), max_length=5, - help_text=_("Language to be used in the generated PDF") - ) - title = CharField(max_length=100, - blank=True, - help_text=_("Name to be used on the title page of the PDF"), - verbose_name=_("Title")) - show_date = BooleanField(default=True, verbose_name=_("Show date"), - help_text=_("True, if the date should be included in the final PDF")) - image = ImageField(verbose_name=_("Title Image"), - help_text=_("Optional title image of the songbook"), - null=True, - blank=True, - upload_to='uploads/') - margin = FloatField(verbose_name=_("Title Image margins"), - help_text=_("Margins for title image, might be needed for some printers"), - default=0) - link = CharField(max_length=300, - blank=True, - help_text=_("Link to include in the PDF"), - verbose_name=_("Link"), - default=settings.PDF_INCLUDE_LINK) + + filename = CharField( + max_length=30, + blank=True, + help_text=_("Filename of the generated PDF, please do not include .pdf"), + verbose_name=_("File name"), + ) + public = BooleanField( + default=True, + help_text=_("True, if the file should be public"), + verbose_name=_("Public file"), + ) + locale = CharField( + choices=settings.LANGUAGES, + verbose_name=_("Language"), + max_length=5, + help_text=_("Language to be used in the generated PDF"), + ) + title = CharField( + max_length=100, + blank=True, + help_text=_("Name to be used on the title page of the PDF"), + verbose_name=_("Title"), + ) + show_date = BooleanField( + default=True, + verbose_name=_("Show date"), + help_text=_("True, if the date should be included in the final PDF"), + ) + image = ImageField( + verbose_name=_("Title Image"), + help_text=_("Optional title image of the songbook"), + null=True, + blank=True, + upload_to="uploads/", + ) + margin = FloatField( + verbose_name=_("Title Image margins"), + help_text=_("Margins for title image, might be needed for some printers"), + default=0, + ) + link = CharField( + max_length=300, + blank=True, + help_text=_("Link to include in the PDF"), + verbose_name=_("Link"), + default=settings.PDF_INCLUDE_LINK, + ) def copy_options(self, options: "PDFOptions"): """Copy all options from another PDFOptions object""" diff --git a/pdf/models/request.py b/pdf/models/request.py index 7674397..6f172bc 100644 --- a/pdf/models/request.py +++ b/pdf/models/request.py @@ -2,8 +2,23 @@ from typing import List from django.core.validators import MinValueValidator -from django.db.models import Model, DateField, DateTimeField, IntegerField, FileField, CharField, TextChoices, \ - ManyToManyField, ForeignKey, CASCADE, SET_NULL, CheckConstraint, Q, PositiveIntegerField, TextField +from django.db.models import ( + Model, + DateField, + DateTimeField, + IntegerField, + FileField, + CharField, + TextChoices, + ManyToManyField, + ForeignKey, + CASCADE, + SET_NULL, + CheckConstraint, + Q, + PositiveIntegerField, + TextField, +) from django.utils.translation import gettext_lazy as _ from backend.models import Song @@ -16,33 +31,32 @@ class RequestType(TextChoices): """Type of PDF Request""" - EVENT = 'EV', _('Automated') - MANUAL = 'MA', _('Manual') + + EVENT = "EV", _("Automated") + MANUAL = "MA", _("Manual") class Status(TextChoices): """Status of PDF Request""" - QUEUED = "QU", _('Queued') - SCHEDULED = "SC", _('Scheduled') - IN_PROGRESS = "PR", _('In progress') + + QUEUED = "QU", _("Queued") + SCHEDULED = "SC", _("Scheduled") + IN_PROGRESS = "PR", _("In progress") DONE = "DO", _("Done") FAILED = "FA", _("Failed") class PDFRequest(PDFOptions): """Request for PDF generation""" -# created_date = DateField(auto_now_add=True, editable=False) + + # created_date = DateField(auto_now_add=True, editable=False) update_date = DateTimeField(auto_now=True) type = CharField( max_length=2, choices=RequestType.choices, default=RequestType.EVENT, ) - status = CharField( - max_length=2, - choices=Status.choices, - default=Status.QUEUED - ) + status = CharField(max_length=2, choices=Status.choices, default=Status.QUEUED) time_elapsed = IntegerField(null=True) progress = IntegerField(default=0) file = FileField(null=True, storage=fs) @@ -56,13 +70,15 @@ def get_songs(self) -> List[Song]: class Meta: constraints = [ - CheckConstraint(check=Q(type=RequestType.MANUAL) | Q(category__isnull=False), - name='automated_category_present'), + CheckConstraint( + check=Q(type=RequestType.MANUAL) | Q(category__isnull=False), + name="automated_category_present", + ), ] ordering = ["-update_date"] -def transform_song(pdf_song: 'PDFSong') -> Song: +def transform_song(pdf_song: "PDFSong") -> Song: """Mapping function that maps PDFSong into Song""" song = pdf_song.song song.song_number = pdf_song.song_number @@ -71,9 +87,10 @@ def transform_song(pdf_song: 'PDFSong') -> Song: class PDFSong(Model): """Through table for PDFRequest and Song""" + song = ForeignKey(Song, on_delete=CASCADE) request = ForeignKey(PDFRequest, on_delete=CASCADE) song_number = PositiveIntegerField(validators=[MinValueValidator(1)], verbose_name=_("Song number")) class Meta: - unique_together = ['song_number', 'request', 'song'] + unique_together = ["song_number", "request", "song"] diff --git a/pdf/storage.py b/pdf/storage.py index f90eee3..c05242a 100644 --- a/pdf/storage.py +++ b/pdf/storage.py @@ -10,6 +10,7 @@ class DateOverwriteStorage(FileSystemStorage): File storage system that saves files into folders based on current date and overwrites already existing files """ + def get_available_name(self, name, max_length=None): parts = list(Path(name).parts) date = datetime.now().strftime("%y%m%d") diff --git a/pdf/urls.py b/pdf/urls.py index ed7b85b..8243d00 100644 --- a/pdf/urls.py +++ b/pdf/urls.py @@ -1,15 +1,22 @@ """Url configuration for PDF app""" from django.urls import path -from pdf.views import RequestListView, RequestSongSelectorView, RequestNumberSelectView, RequestRemoveFileView, \ - RequestRegenerateView, WaitForPDFView, RenderInfoView +from pdf.views import ( + RequestListView, + RequestSongSelectorView, + RequestNumberSelectView, + RequestRemoveFileView, + RequestRegenerateView, + WaitForPDFView, + RenderInfoView, +) urlpatterns = [ - path('list', RequestListView.as_view(), name="list"), - path('new', RequestSongSelectorView.as_view(), name="new"), - path('assign', RequestNumberSelectView.as_view(), name="assign"), - path('wait/', WaitForPDFView.as_view(), name="wait"), - path('info/', RenderInfoView.as_view(), name="info"), - path('remove_file/', RequestRemoveFileView.as_view(), name="remove_file"), - path('regenerate/', RequestRegenerateView.as_view(), name="regenerate") + path("list", RequestListView.as_view(), name="list"), + path("new", RequestSongSelectorView.as_view(), name="new"), + path("assign", RequestNumberSelectView.as_view(), name="assign"), + path("wait/", WaitForPDFView.as_view(), name="wait"), + path("info/", RenderInfoView.as_view(), name="info"), + path("remove_file/", RequestRemoveFileView.as_view(), name="remove_file"), + path("regenerate/", RequestRegenerateView.as_view(), name="regenerate"), ] diff --git a/pdf/utils.py b/pdf/utils.py index ab54226..5a93f1d 100644 --- a/pdf/utils.py +++ b/pdf/utils.py @@ -23,12 +23,12 @@ def regenerate_pdf_request(request, category): """Regenerates the PDF request with the newest info""" with transaction.atomic(): PDFSong.objects.filter(request=request).delete() - PDFSong.objects.bulk_create([ - PDFSong(request=request, - song=song, - song_number=song_number + 1) - for song_number, song in enumerate(category.song_set.filter(archived=False).all()) - ]) + PDFSong.objects.bulk_create( + [ + PDFSong(request=request, song=song, song_number=song_number + 1) + for song_number, song in enumerate(category.song_set.filter(archived=False).all()) + ] + ) return request @@ -36,19 +36,18 @@ def generate_new_pdf_request(category): """Returns PDFRequest for a category""" with transaction.atomic(): scheduled_times = PDFRequest.objects.filter(status=Status.SCHEDULED, type=RequestType.EVENT).values_list( - 'scheduled_at', flat=True) - request = PDFRequest(type=RequestType.EVENT, - status=Status.SCHEDULED, - category=category) + "scheduled_at", flat=True + ) + request = PDFRequest(type=RequestType.EVENT, status=Status.SCHEDULED, category=category) request.copy_options(category) request.filename = request.filename or get_filename(category) request.save() - PDFSong.objects.bulk_create([ - PDFSong(request=request, - song=song, - song_number=song_number + 1) - for song_number, song in enumerate(category.song_set.filter(archived=False).all()) - ]) + PDFSong.objects.bulk_create( + [ + PDFSong(request=request, song=song, song_number=song_number + 1) + for song_number, song in enumerate(category.song_set.filter(archived=False).all()) + ] + ) time = generate_unique_time(scheduled_times, timedelta(minutes=30)) @@ -58,11 +57,11 @@ def generate_new_pdf_request(category): return request + def generate_unique_time(times, delta: timedelta): """Generate unique time, so jobs will not clash on scheduler""" time = timezone.now() + delta for _ in range(len(times) + 1): - valid = True for existing_time in times: if not existing_time: @@ -75,8 +74,9 @@ def generate_unique_time(times, delta: timedelta): time = time + delta raise AttributeError("Unable to assign unique generation time") + def get_filename(category): """Returns filename for category based on its locale""" with translation.override(category.locale): - text = gettext('songbook') + text = gettext("songbook") return f"{text}-{category.name}" diff --git a/pdf/views.py b/pdf/views.py index 0a8939a..c6620d3 100644 --- a/pdf/views.py +++ b/pdf/views.py @@ -20,17 +20,19 @@ from pdf.models.request import PDFRequest, RequestType, Status -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class RequestListView(ListView): """Lists all the requests""" + model = PDFRequest template_name = "pdf/requests/list.html" context_object_name = "requests" -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class RequestRegenerateView(View, SingleObjectMixin): """Regenerates the PDF request""" + model = PDFRequest # pylint: disable=invalid-name, unused-argument @@ -48,9 +50,10 @@ def get(self, request, pk): return redirect("pdf:list") -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class RequestRemoveFileView(View, SingleObjectMixin): """Removes file from request""" + model = PDFRequest # pylint: disable=invalid-name, unused-argument @@ -58,8 +61,10 @@ def get(self, request, pk): """Processes the request""" obj = self.get_object() if not obj.file: - messages.error(request, _("Unable to remove file from request %(id)s that doesn't have one") - % {"id": obj.id}) + messages.error( + request, + _("Unable to remove file from request %(id)s that doesn't have one") % {"id": obj.id}, + ) return redirect("pdf:list") name = obj.file.name obj.file.delete() @@ -70,9 +75,10 @@ def get(self, request, pk): return redirect("pdf:list") -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class RequestSongSelectorView(ListView): """Starts process of creating new PDFRequest by selecting songs for the request""" + model = Song context_object_name = "songs" template_name = "pdf/requests/select.html" @@ -85,15 +91,12 @@ def get_context_data(self, *, object_list=None, **kwargs): return ctx -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class RequestNumberSelectView(TemplateResponseMixin, View): """Assign song numbers for PDF request""" + template_name = "pdf/requests/assign.html" - PDFSongFormset = formset_factory(PDFSongForm, - formset=BasePDFSongFormset, - min_num=1, - validate_min=True, - extra=0) + PDFSongFormset = formset_factory(PDFSongForm, formset=BasePDFSongFormset, min_num=1, validate_min=True, extra=0) def render_to_response(self, context, **response_kwargs): context.update() @@ -111,14 +114,10 @@ def get(self, request, *args, **kwargs): return HttpResponseBadRequest(_("You need to select at least one song")) form = RequestForm(instance=PDFRequest(type=RequestType.MANUAL), prefix="request") - formset = self.PDFSongFormset(prefix="songs", - initial=[ - {'name': song.name, - 'song_number': number + 1, - 'song': song} - for number, song - in enumerate(songs) - ]) + formset = self.PDFSongFormset( + prefix="songs", + initial=[{"name": song.name, "song_number": number + 1, "song": song} for number, song in enumerate(songs)], + ) return self.render_to_response({"form": form, "formset": formset}) # pylint: disable=unused-argument @@ -134,8 +133,10 @@ def post(self, request, *args, **kwargs): for form in formset: form.instance.request = request form.instance.save() - messages.success(self.request, - _("PDF Request with id %(id)s was successfully created") % {'id': request.id}) + messages.success( + self.request, + _("PDF Request with id %(id)s was successfully created") % {"id": request.id}, + ) generate_pdf_job.delay(request) return redirect("pdf:wait", request.id) return self.form_invalid(form, formset) @@ -147,6 +148,7 @@ def form_invalid(self, form, formset): class WaitForPDFView(DetailView): """Shows wait page for PDF generation""" + model = PDFRequest context_object_name = "pdf" template_name = "pdf/requests/wait.html" @@ -154,14 +156,17 @@ class WaitForPDFView(DetailView): class RenderInfoView(View, SingleObjectMixin): """Returns JSON response containing info about PDFRequest""" + model = PDFRequest def get(self, request, *args, **kwargs): """Process GET request""" request = self.get_object() ready = request.status == Status.DONE - return JsonResponse({ - "ready": request.status == Status.DONE, - "progress": request.progress, - "link": request.file.url if ready else None} + return JsonResponse( + { + "ready": request.status == Status.DONE, + "progress": request.progress, + "link": request.file.url if ready else None, + } ) diff --git a/pylint-check.sh b/pylint-check.sh deleted file mode 100755 index be5e563..0000000 --- a/pylint-check.sh +++ /dev/null @@ -1 +0,0 @@ -pipenv run pylint backend/ chords/ pdf/ frontend/ category/ analytics/ --django-settings-module=chords.settings.development \ No newline at end of file diff --git a/pylintrc b/pylintrc deleted file mode 100644 index 9f5fe09..0000000 --- a/pylintrc +++ /dev/null @@ -1,14 +0,0 @@ -[FORMAT] -# Maximum number of characters on a single line. -max-line-length=120 - -[DESIGN] -# Maximum number of parents for a class (see R0901). -max-parents=12 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=1 - -[MAIN] -ignore-patterns=\d{4}_.*?.py -load-plugins = pylint_django \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5570675 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,59 @@ +[tool.poetry] +name = "chords" +version = "0.1.0" +description = "Song book written in Django, supporting offline viewing" +authors = ["Petr Hála "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.9" +django = "==4.1.10" +django-bootstrap4 = "*" +dj-datatables-view = "*" +django-debug-toolbar = "*" +django-markdownx = ">=4.0.0b1" +django-redis = "*" +django-rq = "*" +django-sass-processor = "*" +django-simple-menu = "*" +django-weasyprint = "*" +weasyprint = ">=53.0" +Pillow = "*" +gunicorn = "*" +gevent = "*" +markdown = "*" +markdown3-newtab = "*" +netifaces = "*" +psycopg2-binary = "*" +django-compressor = "*" +libsass = "*" + +[tool.poetry.group.dev.dependencies] +pylint = "*" +pylint-django = "*" +black = "*" + +[tool.black] +line-length = 120 + +# pylint +[tool.pylint.FORMAT] +max-line-length = 120 + +[tool.pylint.DESIGN] +min-public-methods=1 +max-parents=12 +disable = [ + "duplicate-code", # reports false alarms AND can't be disabled locally; pylint issue #214 + "fixme", # ignore TODOs + "redefined-outer-name", + "too-many-arguments", + "too-few-public-methods"] + +[tool.pylint.MAIN] +ignore-patterns= "\\d{4}_.*?.py" +load-plugins = "pylint_django" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api"