Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add import functionality from .qsp, Quelea archive #39

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion backend/forms.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
2 changes: 2 additions & 0 deletions backend/menus.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
5 changes: 5 additions & 0 deletions backend/templates/songs/import.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{% extends "base/form.html" %}
{% load i18n %}

{% block title %} {% trans "Song Editor" %} {% endblock %}
{% block header %} {% trans "Song Editor" %} {% endblock %}
3 changes: 2 additions & 1 deletion backend/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
from django.urls import path

from backend.views import SongCreateView, SongUpdateView, SongDeleteView, SongsDatatableView, \
IndexSongListView
IndexSongListView, UploadView

urlpatterns = [
path('', IndexSongListView.as_view(), name="index"),
path('add', SongCreateView.as_view(), name="add"),
path('edit/<int:pk>', SongUpdateView.as_view(), name="edit"),
path('delete/<int:pk>', SongDeleteView.as_view(), name="delete"),
path('api/songs', SongsDatatableView.as_view(), name="songs"),
path('import', UploadView.as_view(), name="import"),
]
63 changes: 61 additions & 2 deletions backend/views.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
"""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
from django.contrib.auth.decorators import login_required
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
Expand Down Expand Up @@ -120,3 +123,59 @@ 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"""

with zipfile.ZipFile(cleaned_data['file']) as file:
songs = {name: file.read(name) for name in file.namelist()}

lyrics = []

for name in songs.keys():
song = ET.fromstring(songs[name])
lyrics_lines = []
for child in song.findall('.//lyrics'):
if child.text is not None:
lyrics_lines.append(child.text)

lyrics.append((name, '\n\n'.join(lyrics_lines)))

num_songs = 0

for (name, text) in lyrics:
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)
regenerate_pdf(song_obj)
regenerate_prerender(song_obj)
num_songs += 1

return num_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)

8 changes: 6 additions & 2 deletions frontend/templates/base/form.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
{% load bootstrap4 %}

{% block framed_body %}
<form method="post">
{% if form.is_multipart %}
<form enctype="multipart/form-data" method="post">
{% else %}
<form method="post">
{% endif %}
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">{% trans "Submit" %}</button>
{% endbuttons %}
</form>
{{ form.media }}
{% endblock %}
{% endblock %}