diff --git a/backend/forms.py b/backend/forms.py index dfa758a..6c4fe04 100644 --- a/backend/forms.py +++ b/backend/forms.py @@ -1,5 +1,5 @@ """Forms for backend app""" -from django.forms import ModelForm, ModelMultipleChoiceField, CheckboxSelectMultiple +from django.forms import ModelForm, ModelMultipleChoiceField, CheckboxSelectMultiple, Form, FileField from backend.models import Song from category.models import Category @@ -13,3 +13,8 @@ class Meta: model = Song # pylint: disable=modelform-uses-exclude exclude = ["prerendered_web", "prerendered_pdf"] + +class UploadFileForm(Form): + """File upload form with selection of category""" + categories = ModelMultipleChoiceField(widget=CheckboxSelectMultiple, queryset=Category.objects.all()) + file = FileField(required=True, allow_empty_file=False) diff --git a/backend/menus.py b/backend/menus.py index 6601d70..810ae0d 100644 --- a/backend/menus.py +++ b/backend/menus.py @@ -9,6 +9,8 @@ reverse("category:list")), MenuItem(_("Add a song"), reverse("backend:add")), + MenuItem(_("Import Quelue archive"), + reverse("backend:import")), MenuItem(_("Add Songbook"), reverse("category:add")), MenuItem(_("Analytics"), diff --git a/backend/templates/songs/import.html b/backend/templates/songs/import.html new file mode 100644 index 0000000..5854f7c --- /dev/null +++ b/backend/templates/songs/import.html @@ -0,0 +1,5 @@ +{% extends "base/form.html" %} +{% load i18n %} + +{% block title %} {% trans "Song Editor" %} {% endblock %} +{% block header %} {% trans "Song Editor" %} {% endblock %} diff --git a/backend/urls.py b/backend/urls.py index 933e8b5..950d643 100644 --- a/backend/urls.py +++ b/backend/urls.py @@ -2,7 +2,7 @@ from django.urls import path from backend.views import SongCreateView, SongUpdateView, SongDeleteView, SongsDatatableView, \ - IndexSongListView + IndexSongListView, UploadView urlpatterns = [ path('', IndexSongListView.as_view(), name="index"), @@ -10,4 +10,5 @@ path('edit/', SongUpdateView.as_view(), name="edit"), path('delete/', SongDeleteView.as_view(), name="delete"), path('api/songs', SongsDatatableView.as_view(), name="songs"), + path('import', UploadView.as_view(), name="import"), ] diff --git a/backend/views.py b/backend/views.py index 1f10fa8..fc813df 100644 --- a/backend/views.py +++ b/backend/views.py @@ -1,6 +1,8 @@ """Views for backend app""" import json from typing import Dict +import zipfile +import xml.etree.ElementTree as ET from django.conf import settings from django.contrib import messages @@ -8,14 +10,15 @@ from django.contrib.messages.views import SuccessMessageMixin from django.core.serializers.json import DjangoJSONEncoder from django.forms import model_to_dict +from django.http import HttpResponseRedirect from django.urls import reverse_lazy, reverse from django.utils.decorators import method_decorator from django.utils.translation import gettext_lazy as _, gettext_noop -from django.views.generic import ListView, CreateView, UpdateView, DeleteView +from django.views.generic import ListView, CreateView, UpdateView, DeleteView, FormView from django_datatables_view.base_datatable_view import BaseDatatableView from analytics.views import AnalyticsMixin -from backend.forms import SongForm +from backend.forms import SongForm, UploadFileForm from backend.models import Song from backend.utils import regenerate_pdf, regenerate_prerender from category.models import Category @@ -120,3 +123,61 @@ class SongsDatatableView(BaseDatatableView): model = Song max_display_length = 500 columns = ["name", "author", "capo"] + + +def quelea_song_import(cleaned_data): + """Read data from file in cleaned_data, and import all songs in qsp archive""" + + def read_quelea_file(filename): + with zipfile.ZipFile(filename) as file: + return {name: file.read(name) for name in file.namelist()} + + def get_lyrics(mapp): + ret = [] + for name in mapp.keys(): + song = ET.fromstring(mapp[name]) + lyrics_lines = [] + for child in song.findall('.//lyrics'): + if child.text is not None: + lyrics_lines.append(child.text) + + ret.append((name, '\n\n'.join(lyrics_lines))) + return ret + + songs = read_quelea_file(cleaned_data['file']) + lyric = get_lyrics(songs) + + songs = 0 + for (name, text) in lyric: + clean_name = name.rstrip('.xml') + + song_obj = Song(name=clean_name, text=text) + song_obj.save() + + list_ids = [f.pk for f in cleaned_data['categories']] + + song_obj.categories.set(list_ids) + songs += 1 + + return songs + + +@method_decorator(login_required, name='dispatch') +class UploadView(FormView): + """View to recieve qsp file and import all songs""" + + form_class = UploadFileForm + template_name = 'songs/import.html' + + success_url = reverse_lazy('backend:index') + + # def get_success_url(self): + # regenerate_pdf(self.object) + # regenerate_prerender(self.object) + # return super().get_success_url() + + def form_valid(self, form): + num = quelea_song_import(form.cleaned_data) + messages.success(self.request, f"{num} songs imported.") + return super().form_valid(form) + diff --git a/chords/settings/base.py b/chords/settings/base.py index c15602e..91f1bc3 100644 --- a/chords/settings/base.py +++ b/chords/settings/base.py @@ -101,7 +101,7 @@ STATICFILES_FINDERS = [ 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - 'sass_processor.finders.CssFinder' + 'sass_processor.finders.CssFinder', ] COMPRESS_ENABLED = True diff --git a/frontend/templates/base/form.html b/frontend/templates/base/form.html index afa938e..c633b96 100644 --- a/frontend/templates/base/form.html +++ b/frontend/templates/base/form.html @@ -3,7 +3,11 @@ {% load bootstrap4 %} {% block framed_body %} -
+ {% if form.is_multipart %} + + {% else %} + + {% endif %} {% csrf_token %} {% bootstrap_form form %} {% buttons %} @@ -11,4 +15,4 @@ {% endbuttons %}
{{ form.media }} -{% endblock %} \ No newline at end of file +{% endblock %}