Skip to content

Commit 6598cae

Browse files
authored
Merge pull request #521 from digitalfabrik/feature/export-to-csv
Feature/export to csv
2 parents 6c6d5ff + e5da7c6 commit 6598cae

6 files changed

Lines changed: 229 additions & 19 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ UNRELEASED
22
----------
33

44
* [ [#353](https://github.com/digitalfabrik/lunes-cms/issues/353) ] Filter feedback by creator of related objects
5-
6-
7-
2024.5.1
8-
--------
5+
* [ [#468](https://github.com/digitalfabrik/lunes-cms/issues/468) ] Excel list out of existing vocabulary in cms
96

107

118
2024.5.0

lunes_cms/cms/admins/discipline_admin.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
from __future__ import absolute_import, unicode_literals
22

3+
import io
4+
import re
5+
from zipfile import ZipFile
6+
37
from django.contrib import admin
48
from django.db.models import Count, Q
59
from django.db.models.functions import Greatest
10+
from django.http import HttpResponse
611
from django.urls import reverse
712
from django.utils.safestring import mark_safe
813
from django.utils.translation import ugettext_lazy as _
914
from mptt.admin import DraggableMPTTAdmin
15+
from tablib import Dataset
1016

11-
from ..models import Discipline, Static
17+
from ..models import Discipline, Document, Static
18+
from .document_resource import DocumentResource
1219

1320

1421
class DisciplineAdmin(DraggableMPTTAdmin):
@@ -39,7 +46,12 @@ class DisciplineAdmin(DraggableMPTTAdmin):
3946
]
4047
list_display_links = ["indented_title"]
4148
list_filter = ["released"]
42-
actions = ["delete_selected", "make_released", "make_unreleased"]
49+
actions = [
50+
"delete_selected",
51+
"make_released",
52+
"make_unreleased",
53+
"make_export_to_CSV",
54+
]
4355
list_per_page = 25
4456

4557
def get_list_display(self, request):
@@ -269,6 +281,45 @@ def make_unreleased(self, request, queryset):
269281
"""
270282
queryset.update(released=False)
271283

284+
@admin.action(description=_("Export all vocabulary for this discipline to CSV"))
285+
def make_export_to_CSV(self, request, queryset):
286+
"""
287+
Export the documents of the selected disciplines.
288+
289+
:param request: current user request
290+
:type request: django.http.request
291+
:param queryset: current queryset
292+
:type queryset: QuerySet
293+
"""
294+
csvs = {}
295+
296+
for profession in queryset:
297+
if profession.children.exists():
298+
continue
299+
resource = DocumentResource()
300+
301+
dataset = Dataset(
302+
*(
303+
resource.export_resource(obj)
304+
for obj in Document.objects.filter(
305+
training_sets__discipline=profession
306+
)
307+
),
308+
headers=resource.export().headers,
309+
)
310+
311+
csvs[profession] = dataset.csv
312+
313+
zip_buffer = io.BytesIO()
314+
315+
with ZipFile(zip_buffer, "w") as zipfile:
316+
for profession, csv in csvs.items():
317+
zipfile.writestr(f"{make_safe_filename(profession.title)}.csv", csv)
318+
319+
response = HttpResponse(zip_buffer.getvalue(), content_type="application/zip")
320+
response["Content-Disposition"] = 'attachment; filename="Lunes_vocabulary.zip"'
321+
return response
322+
272323
@admin.display(
273324
description=_("released modules"),
274325
ordering="modules_released_order",
@@ -357,3 +408,10 @@ class Media:
357408
"""
358409

359410
js = ("js/image_preview.js",)
411+
412+
413+
def make_safe_filename(unsafe):
414+
"""
415+
Method to create a safe filename with regex.
416+
"""
417+
return re.sub(r"[^a-zA-Z0-9.äöüÄÖÜ]+", "_", unsafe)
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from django.utils.translation import ugettext_lazy as _
2+
from import_export import fields, resources
3+
from import_export.admin import ExportActionMixin
4+
5+
from ..models import Document
6+
from ..models.static import Static
7+
8+
9+
class DocumentResource(resources.ModelResource):
10+
"""
11+
Resource to export Documents from the discipline view.
12+
"""
13+
14+
# pylint: disable=pointless-statement
15+
ExportActionMixin.export_admin_action
16+
17+
# pylint: disable=super-init-not-called
18+
def __init__(self, for_profession=None):
19+
self.for_profession = for_profession
20+
self.relevant_training_sets = (
21+
for_profession.get_nested_training_sets() if for_profession else None
22+
)
23+
24+
word = fields.Field(column_name=_("Word"), attribute="word")
25+
26+
word_type = fields.Field(column_name=_("Word type"), attribute="word_type")
27+
28+
singular_article = fields.Field(
29+
column_name=_("Singular Article"),
30+
attribute="singular_article",
31+
)
32+
33+
def dehydrate_singular_article(self, document):
34+
"""
35+
Method to show the actual singular article and not their integer.
36+
"""
37+
for x in Static.singular_article_choices:
38+
if x[0] == document.singular_article:
39+
return x[1]
40+
return "-"
41+
42+
plural_article = fields.Field(
43+
column_name=_("Plural Article"),
44+
attribute="plural_article_choices",
45+
)
46+
47+
def dehydrate_plural_article(self, document):
48+
"""
49+
Method to show the actual plural article and not their integer.
50+
"""
51+
for x in Static.plural_article_choices:
52+
if x[0] == document.plural_article:
53+
return x[1]
54+
return "-"
55+
56+
has_audio = fields.Field(column_name=_("Has audio?"), attribute="word")
57+
58+
def dehydrate_has_audio(self, document):
59+
"""
60+
Returns yes if audio exists and no if it doesn't.
61+
"""
62+
if bool(document.audio) is True:
63+
return _("Yes")
64+
return _("No")
65+
66+
example_sentence = fields.Field(
67+
column_name=_("Example sentence"), attribute="example_sentence"
68+
)
69+
70+
creation_date = fields.Field(
71+
column_name=_("Creation date"), attribute="creation_date"
72+
)
73+
74+
def dehydrate_creation_date(self, document):
75+
"""
76+
Only show first 16 characters of date (date, and hours & minutes).
77+
"""
78+
return str(document.creation_date)[:16]
79+
80+
training_sets = fields.Field(
81+
column_name=_("Training Sets"), attribute="training_sets"
82+
)
83+
84+
def dehydrate_training_sets(self, document):
85+
"""
86+
Method to get relevant training sets.
87+
"""
88+
relevant_training_sets = (
89+
self.relevant_training_sets
90+
if self.relevant_training_sets
91+
else document.training_sets.values_list("id", flat=True)
92+
)
93+
return " | ".join(
94+
[
95+
t.title
96+
for t in document.training_sets.filter(id__in=relevant_training_sets)
97+
]
98+
)
99+
100+
class Meta:
101+
"""
102+
Meta class of Document resource
103+
"""
104+
105+
model = Document
106+
fields = ()

lunes_cms/core/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"pydub",
6262
"rest_framework",
6363
"qr_code",
64+
"import_export",
6465
]
6566

6667
#: Activated middlewares (see :setting:`django:MIDDLEWARE`)

lunes_cms/locale/de/LC_MESSAGES/django.po

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ msgid ""
77
msgstr ""
88
"Project-Id-Version: PACKAGE VERSION\n"
99
"Report-Msgid-Bugs-To: \n"
10-
"POT-Creation-Date: 2024-05-22 08:52+0000\n"
10+
"POT-Creation-Date: 2024-06-02 09:04+0000\n"
1111
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1212
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1313
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -55,27 +55,31 @@ msgstr "API Keys"
5555
msgid "feedback"
5656
msgstr "Feedback"
5757

58-
#: cms/admins/discipline_admin.py:246
58+
#: cms/admins/discipline_admin.py:258
5959
msgid "Release selected disciplines"
6060
msgstr "Ausgewählte Modulgruppen veröffentlichen"
6161

62-
#: cms/admins/discipline_admin.py:259
62+
#: cms/admins/discipline_admin.py:271
6363
msgid "Unrelease selected disciplines"
6464
msgstr "Ausgewählte Modulgruppen zurückhalten"
6565

66-
#: cms/admins/discipline_admin.py:273
66+
#: cms/admins/discipline_admin.py:284
67+
msgid "Export all vocabulary for this discipline to CSV"
68+
msgstr "Alle Vokabeln dieser Modulgruppe als CSV exportieren"
69+
70+
#: cms/admins/discipline_admin.py:324
6771
msgid "released modules"
6872
msgstr "veröffentlichte Module"
6973

70-
#: cms/admins/discipline_admin.py:294
74+
#: cms/admins/discipline_admin.py:345
7175
msgid "unreleased modules"
7276
msgstr "unveröffentlichte Module"
7377

74-
#: cms/admins/discipline_admin.py:315
78+
#: cms/admins/discipline_admin.py:366
7579
msgid "published words in released modules"
7680
msgstr "veröffentlichte Vokabeln in veröffentlichten Modulen"
7781

78-
#: cms/admins/discipline_admin.py:336 cms/admins/document_admin.py:177
82+
#: cms/admins/discipline_admin.py:387 cms/admins/document_admin.py:177
7983
#: cms/admins/training_set_admin.py:376
8084
msgid "creator group"
8185
msgstr "Besitzergruppe"
@@ -98,6 +102,46 @@ msgstr "Bild"
98102
msgid "singular article"
99103
msgstr "Singular-Artikel"
100104

105+
#: cms/admins/document_resource.py:24
106+
msgid "Word"
107+
msgstr "Vokabel"
108+
109+
#: cms/admins/document_resource.py:26
110+
msgid "Word type"
111+
msgstr "Wortart"
112+
113+
#: cms/admins/document_resource.py:29
114+
msgid "Singular Article"
115+
msgstr "Singular Artikel"
116+
117+
#: cms/admins/document_resource.py:43
118+
msgid "Plural Article"
119+
msgstr "Plural Artikel"
120+
121+
#: cms/admins/document_resource.py:56
122+
msgid "Has audio?"
123+
msgstr "Audio"
124+
125+
#: cms/admins/document_resource.py:63
126+
msgid "Yes"
127+
msgstr "Ja"
128+
129+
#: cms/admins/document_resource.py:64
130+
msgid "No"
131+
msgstr "Nein"
132+
133+
#: cms/admins/document_resource.py:67
134+
msgid "Example sentence"
135+
msgstr "Beispielsatz"
136+
137+
#: cms/admins/document_resource.py:71
138+
msgid "Creation date"
139+
msgstr "Erstellt am"
140+
141+
#: cms/admins/document_resource.py:81
142+
msgid "Training Sets"
143+
msgstr "Module"
144+
101145
#: cms/admins/feedback_admin.py:34
102146
msgid "Mark as read"
103147
msgstr "Als gelesen markieren"
@@ -571,28 +615,31 @@ msgstr "Datei zu groß! Max. 5 MB"
571615
msgid "Only use one file extension!"
572616
msgstr "Nur maximal ein Dateityp erlaubt!"
573617

574-
#: core/settings.py:203
618+
#: core/settings.py:204
575619
msgid "English"
576620
msgstr "Englisch"
577621

578-
#: core/settings.py:204
622+
#: core/settings.py:205
579623
msgid "German"
580624
msgstr "Deutsch"
581625

582-
#: core/settings.py:444 core/settings.py:445 core/settings.py:447
626+
#: core/settings.py:445 core/settings.py:446 core/settings.py:448
583627
msgid "Lunes Administration"
584628
msgstr "Lunes-Verwaltung"
585629

586-
#: core/settings.py:446
630+
#: core/settings.py:447
587631
msgid "Welcome to the vocabulary management of Lunes!"
588632
msgstr "Willkommen bei der Vokabelverwaltung von Lunes!"
589633

590634
#: help/apps.py:13
591635
msgid "help"
592636
msgstr "Hilfe"
593637

594-
#~ msgid "article"
595-
#~ msgstr "Artikel"
638+
#~ msgid "Article"
639+
#~ msgstr "Arikel"
640+
641+
#~ msgid "Export to CSV"
642+
#~ msgstr "Als CSV exportieren"
596643

597644
#~ msgid "assigned & unassigned"
598645
#~ msgstr "Zugewiesen & unzugewiesen"

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ packages =
4646
lunes_cms
4747
install_requires =
4848
django==3.2.21
49+
django-import-export==3.3.7
4950
django-jazzmin==2.6.0
5051
django-mptt==0.15.0
5152
django-qr-code==3.1.1

0 commit comments

Comments
 (0)