WIP: Stáhnout řešení jako ZIP #84

Draft
ledoian wants to merge 2 commits from stahnout_reseni_jako_zip into master
2 changed files with 54 additions and 0 deletions

View file

@ -11,7 +11,9 @@ urlpatterns = [
path('resitel/odevzdana_reseni/', resitel_or_org_required(views.PrehledOdevzdanychReseni.as_view()), name='odevzdavatko_resitel_odevzdana_reseni'),
path('org/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'),
path('org/reseni/zip/', org_required(views.OdevzdanaReseniVZipuView.as_view()), name='odevzdavatko_zip'),
path('org/reseni/rocnik/<int:rocnik>/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'),
path('org/reseni/rocnik/<int:rocnik>/zip/', org_required(views.OdevzdanaReseniVZipuView.as_view()), name='odevzdavatko_zip'),
path('org/reseni/<int:problem>/<int:resitel>/', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'),
path('org/reseni/<int:pk>', org_required(viewMethodSwitch(get=views.EditReseniView.as_view(), post=views.hodnoceniReseniView)), name='odevzdavatko_detail_reseni'),
path('org/reseni/all', org_required(views.SeznamReseniView.as_view())),

View file

@ -10,17 +10,22 @@ from django.shortcuts import redirect, get_object_or_404, render
from django.urls import reverse
from django.db import transaction
from django.db.models import Q
from django.http import HttpResponse
from dataclasses import dataclass
import datetime
from decimal import Decimal
from itertools import groupby
import logging
import os
import tempfile
import zipfile
from . import forms as f
from .forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm
from .models import Hodnoceni, Reseni
from odevzdavatko.templatetags.jmena import jmeno_jako_prefix
from personalni.models import Resitel, Osoba, Organizator
from tvorba.models import Problem, Deadline, Rocnik
from tvorba.utils import resi_v_rocniku
@ -103,6 +108,11 @@ class TabulkaOdevzdanychReseniView(ListView):
# NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy.
self.problemy = self.problemy.non_polymorphic().distinct()
# self.problemy jsou teď už správně, zrelevantníme self.reseni a self.resitele
self.reseni = self.reseni.filter(problem__in=self.problemy).distinct()
if resitele == FiltrForm.RESITELE_RELEVANTNI:
self.resitele = self.resitele.filter(reseni__in=self.reseni).distinct()
self.reseni = self.reseni.filter(cas_doruceni__date__gt=reseni_od, cas_doruceni__date__lte=reseni_do)
if jen_neobodovane:
self.reseni = self.reseni.filter(hodnoceni__body__isnull=True)
@ -116,6 +126,7 @@ class TabulkaOdevzdanychReseniView(ListView):
qs = qs.filter(problem__in=self.problemy, reseni__in=self.reseni, reseni__resitele__in=self.resitele).select_related('reseni', 'problem').prefetch_related('reseni__resitele__osoba').distinct()
# FIXME tohle je ošklivé, na špatném místě a pomalé. Ale moc mě štvalo, že musím hledat správná místa v tabulce.
self.problemy = self.problemy.filter(id__in=qs.values("problem__id"))
# TODO: liší se nějak od `self.problemy = self.problemy.filter(hodnoceni__in=qs)`?
return qs
def get_context_data(self, *args, **kwargs):
@ -175,6 +186,47 @@ class TabulkaOdevzdanychReseniView(ListView):
return ctx
# Intuitivně se mi dědičnost nelíbí, neumím říct přesně proč… (Zhruba:
# vykoná/přikládá se spousta kódu, který ale souvisí jen s HTML, tím, že si
# píšeme vlastní .get(). Lepší by bylo společné ne-HTML části (e.g.
# inicializuj_osy_tabulky) vyrazit ven a v obou Views jen použít…)
class OdevzdanaReseniVZipuView(TabulkaOdevzdanychReseniView):
def get(self, request, *args, **kwargs):
# Inspirováno implementací django.views.generic.list.BaseListView
self.object_list = self.get_queryset()
# Teď už máme i `self.problemy`.
with tempfile.TemporaryDirectory() as d:
print(f'DBG: {d=}')
zfname = f"{d}/reseni.zip"
with zipfile.ZipFile(zfname, 'w', compression=zipfile.ZIP_LZMA) as zf: # `zip` is builtin :-/
print(f'DBG: .{self.reseni.count()=}')
# TODO: data z tabulky
for r in self.reseni:
if len(r.resitele.all()) < 1:
logger.error(f'Řešení {r.id} nemá řešitele??!!')
continue
# DBG!
if len(r.prilohy.all()) < 1: continue
# TODO: komentáře jmen souborů
# Pro konzistenci imitujeme `data-alt-filename="{{object.resitele.first.osoba | jmeno_jako_prefix }}_{{ object.id }}_{{ priloha.split | last}}"` z templates/odevzdavatko/detail.html.
jmeno_slozky = f'{jmeno_jako_prefix(r.resitele.first().osoba)}_{r.id}'
zf.mkdir(jmeno_slozky)
slozka = zipfile.Path(zf, jmeno_slozky)
print(f'DBG: .({jmeno_slozky=}, {r.prilohy.count()=})')
for pr in r.prilohy.all():
jmeno_souboru = f'{jmeno_slozky}_{pr.split()[-1]}'
print(f'DBG: .({os.path.join(jmeno_slozky, jmeno_souboru)=})')
zf.write(pr.soubor.path, os.path.join(jmeno_slozky, jmeno_souboru))
print(f'DBG: Done, sending')
# close&open k provedení všech zápisů
with open(zfname, 'rb') as zf:
print(f'DBG: .')
response = HttpResponse(zf.read(), content_type='application/zip')
response['Content-Disposition'] = 'attachment; filename="reseni.zip"'
print(f'DBG: .')
return response
# Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji?
class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixin, View):
"""Rozskok mezi více řešeními téhož problému od téhož řešitele.