question as csv

This commit is contained in:
Holger Sielaff
2024-07-13 14:38:42 +02:00
parent e7b2dff233
commit 1531b0f6d3
6 changed files with 99 additions and 44 deletions

View File

@@ -1,5 +1,5 @@
import csv
import json import json
import logging
import os import os
from colorfield.fields import ColorField from colorfield.fields import ColorField
@@ -12,6 +12,7 @@ from filer.fields.file import FilerFileField
from lib.core.db.models.base import SharedPermissionBase from lib.core.db.models.base import SharedPermissionBase
from lib.core.db.models.mixins import DateAware, AuthorAware, DescriptionAware, NameAware, PublishedAware from lib.core.db.models.mixins import DateAware, AuthorAware, DescriptionAware, NameAware, PublishedAware
from tablequizwiki.settings import BASE_DIR
class MediaFile(NameAware, DateAware, AuthorAware, DescriptionAware): class MediaFile(NameAware, DateAware, AuthorAware, DescriptionAware):
@@ -88,15 +89,13 @@ class Question(DateAware, AuthorAware, PublishedAware, DescriptionAware):
| models.Q(buzzword__icontains=term) \ | models.Q(buzzword__icontains=term) \
| models.Q(labels__name__icontains=term) | models.Q(labels__name__icontains=term)
@staticmethod @staticmethod
def get_by_tearchterm(term, queryset=None): def get_by_tearchterm(term, queryset=None):
if not queryset: if not queryset:
queryset = Question.objects queryset = Question.objects
return queryset.filter(Question.searchdomain(term)).annotate(cnt=models.Count('id')) return queryset.filter(Question.searchdomain(term)).annotate(cnt=models.Count('id'))
def to_view(self, for_players: bool = False):
def to_view(self, for_players: bool = False):
ret = {} ret = {}
ret['name'] = '{}{}'.format(self.name, f'({self.buzzword})' if self.buzzword else '') ret['name'] = '{}{}'.format(self.name, f'({self.buzzword})' if self.buzzword else '')
ret['question'] = self.question ret['question'] = self.question
@@ -111,15 +110,39 @@ def to_view(self, for_players: bool = False):
for link in self.links.all(): for link in self.links.all():
ret.setdefault('links', []).append(link.url) ret.setdefault('links', []).append(link.url)
ret['question'] = mark_safe(ret['question'].replace('\n', '\n<br />')) ret['question'] = mark_safe(ret['question'].replace('\n', '\n<br />'))
logging.error(ret)
return ret return ret
@property
def zippath(self):
return str(BASE_DIR) + f'/filestore/zip/question.{self.id}.zip'
def default_json(o): @property
def pdfpath(self):
return str(BASE_DIR) + f'/filestore/pdf/question.{self.id}.pdf'
@property
def csvpath(self):
return str(BASE_DIR) + f'/filestore/csv/question.{self.id}.csv'
def default_json(o):
if isinstance(o, (MediaFile,)): if isinstance(o, (MediaFile,)):
return str(o) return str(o)
return json.dumps(model_to_dict(o)) return json.dumps(model_to_dict(o))
def to_csv(self, fh=None):
if fh:
cf = fh
else:
cf = open(self.csvpath, 'w')
items = self.to_view()
keys = items.keys()
dw = csv.DictWriter(cf, fieldnames=list(keys))
dw.writeheader()
dw.writerow(items)
if not fh:
cf.close()
return cf if fh else self.csvpath
class QuestionVersion(DateAware, AuthorAware): class QuestionVersion(DateAware, AuthorAware):
question = models.ForeignKey(Question, on_delete=models.SET_NULL, null=True) question = models.ForeignKey(Question, on_delete=models.SET_NULL, null=True)

View File

@@ -1,5 +1,6 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block content %} {% block content %}
<h3>Questions</h3>
<form action="/questions" method="get" id="search_q_form"> <form action="/questions" method="get" id="search_q_form">
<div id="question-search-field" class="row mb-5 mt-5"> <div id="question-search-field" class="row mb-5 mt-5">
<div class="col-12 p-0"> <div class="col-12 p-0">
@@ -23,6 +24,7 @@
<div class="col-3">Question</div> <div class="col-3">Question</div>
<div class="col-1">Files</div> <div class="col-1">Files</div>
<div class="col-1">Labels</div> <div class="col-1">Labels</div>
<div class="col-1"><i class="fa fa-download"></i></div>
</div> </div>
{% for q in items %} {% for q in items %}
<div class="row p-2"> <div class="row p-2">
@@ -36,6 +38,9 @@
{% if not forloop.last %},{% endif %} {% if not forloop.last %},{% endif %}
{% endfor %} {% endfor %}
</div> </div>
<div class="col-1">
<a href="/questions/{{ q.id }}/csv" target="_blank">CSV</a>
</div>
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}

View File

@@ -3,5 +3,6 @@ from content.views import public
urlpatterns = [ urlpatterns = [
path('questions/', public.search_question, name='search-questions'), path('questions/', public.search_question, name='search-questions'),
path('questions/<int:id>/csv/', public.as_csv, name='csv-question'),
path('', public.search_question, name='search-questions'), path('', public.search_question, name='search-questions'),
] ]

View File

@@ -1,3 +1,4 @@
from django.http import HttpResponseNotFound, HttpResponse, HttpResponseBadRequest
from django.shortcuts import render from django.shortcuts import render
from content.models import Question from content.models import Question
@@ -12,3 +13,14 @@ def search_question(request):
else: else:
items = published.all()[:10] items = published.all()[:10]
return render(request, 'questions.html', {'items': items}) return render(request, 'questions.html', {'items': items})
def as_csv(request, **kwargs):
try:
q = Question.objects.get(id=kwargs['id'])
response = HttpResponse(content_type='text/csv')
return q.to_csv(fh=response)
except KeyError:
return HttpResponseBadRequest('No id given')
except Question.DoesNotExist:
return HttpResponseNotFound('Question not found')

View File

@@ -62,13 +62,18 @@ class Quiz(AuthorAware, DateAware, PublishedAware):
return self.pdfpath return self.pdfpath
def to_csv(self, fh=None): def to_csv(self, fh=None):
with open(self.csvpath, 'w') as cf: if fh:
cf = fh
else:
cf = open(self.csvpath, 'w')
items = self.to_view()['items'] items = self.to_view()['items']
keys = items[0].keys() keys = items[0].keys()
dw = csv.DictWriter(cf, fieldnames=list(keys)) dw = csv.DictWriter(cf, fieldnames=list(keys))
dw.writeheader() dw.writeheader()
dw.writerows(items) dw.writerows(items)
return self.csvpath if not fh:
cf.close()
return cf if fh else self.csvpath
def to_zip(self, fh=None): def to_zip(self, fh=None):
with zipfile.ZipFile(self.zippath, 'w') as zf: with zipfile.ZipFile(self.zippath, 'w') as zf:

View File

@@ -11,31 +11,40 @@
-moz-border-radius: 4px; -moz-border-radius: 4px;
border-radius: 4px; border-radius: 4px;
} }
.input-group-unstyled .input-group-addon { .input-group-unstyled .input-group-addon {
border-radius: 4px; border-radius: 4px;
border: 0px; border: 0px;
background-color: transparent; background-color: transparent;
} }
</style> </style>
<link href="https://ka-f.fontawesome.com/releases/v6.5.2/css/free.min.css?token=5f65fb5684" rel="stylesheet" /> <link href="https://ka-f.fontawesome.com/releases/v6.5.2/css/free.min.css?token=5f65fb5684" rel="stylesheet"/>
<link href="https://ka-f.fontawesome.com/releases/v6.5.2/css/free-v4-shims.min.css?token=5f65fb5684" rel="stylesheet" /> <link href="https://ka-f.fontawesome.com/releases/v6.5.2/css/free-v4-shims.min.css?token=5f65fb5684" rel="stylesheet"/>
<link href="https://ka-f.fontawesome.com/releases/v6.5.2/css/free-v5-font-face.min.css?token=5f65fb5684" rel="stylesheet" /> <link href="https://ka-f.fontawesome.com/releases/v6.5.2/css/free-v5-font-face.min.css?token=5f65fb5684" rel="stylesheet"/>
<link href="https://ka-f.fontawesome.com/releases/v6.5.2/css/free-v4-font-face.min.css?token=5f65fb5684" rel="stylesheet" /> <link href="https://ka-f.fontawesome.com/releases/v6.5.2/css/free-v4-font-face.min.css?token=5f65fb5684" rel="stylesheet"/>
{# <script src="https://kit.fontawesome.com/5f65fb5684.js" crossorigin="anonymous"></script> #} {# <script src="https://kit.fontawesome.com/5f65fb5684.js" crossorigin="anonymous"></script> #}
</head> </head>
<body> <body>
<div class="container mt-2"> <div class="container mt-2">
<header> <header>
<div class="row">
<div class="col-5">
<h1>Tablequiz DB</h1> <h1>Tablequiz DB</h1>
<nav> </div>
<a class="nav-item" href="/questions/">Questions</a> <div class="col-7" style="vertical-align: bottom">
<a class="nav-item" href="/quizes/">Quizes</a> <nav style="vertical-align: bottom; width: auto; justify-content: flex-end" class="navbar right">
<a class="nav-item p-3 btn btn-outline-dark mx-2" href="/questions/">Questions</a>
<a class="nav-item p-3 btn btn-outline-dark mx-2" href="/quizes/">Quizes</a>
</nav> </nav>
</div>
</div>
</header> </header>
<hr class="my-3 mx-0 p-0" />
{% block content %} {% block content %}
{% endblock %} {% endblock %}
</div> </div>
</body> </body>
</html> </html>