Compare commits

..

2 commits

Author SHA1 Message Date
Pavel "LEdoian" Turinsky
dd0872cc83 Inicialzujeme osy na správnou sadu objektů, snad 2025-01-21 23:52:30 +01:00
Pavel "LEdoian" Turinsky
cd914f4524 Hrubá implementace stahování jako ZIP 2025-01-15 01:18:41 +01:00
27 changed files with 109 additions and 247 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 617 B

View file

@ -95,7 +95,7 @@ function safe_checkout_branch {
echo >&2 "Změna v $SCRIPT, prosím pullni manuálně" echo >&2 "Změna v $SCRIPT, prosím pullni manuálně"
exit 1 exit 1
fi fi
git checkout "$BRANCH" -- git checkout "$BRANCH"
git pull git pull
git clean -f git clean -f
} }

View file

@ -1,6 +1,6 @@
# Tento soubor slouží pouze pro shell a podobné. Nikde neimportovat v kódu! # Tento soubor slouží pouze pro shell a podobné. Nikde neimportovat v kódu!
print("Naimportoval jsi `mamweb.vsechno`. Pevně věřím, že to nebylo nikde v kódu. Díky.") print("Naimportoval jsi `seminar.models`. Pevně věřím, že to nebylo nikde v kódu. Díky.")
from galerie.models import * from galerie.models import *
from header_fotky.models import * from header_fotky.models import *

View file

@ -65,9 +65,6 @@ class Reseni(SeminarModelBase):
def absolute_url(self): def absolute_url(self):
return "https://" + str(get_current_site(None)) + self.verejne_url() return "https://" + str(get_current_site(None)) + self.verejne_url()
def resitel_url(self):
return f'https://{get_current_site(None)}{reverse_lazy("odevzdavatko_resitel_reseni", args=[self.id])}'
# má OneToOneField s: # má OneToOneField s:
# Konfera # Konfera

View file

@ -191,7 +191,7 @@ Sloupce:
</ul> </ul>
</li> </li>
<li>Pokud nemáš důvod, deadline neměň. Sloupeček s deadlinem znamená, do kterého deadlinu se započítají body (nemusí se shodovat s deadlinem řešení).</li> <li>Pokud nemáš důvod, deadline neměň. Sloupeček s deadlinem znamená, do kterého deadlinu se započítají body (nemusí se shodovat s deadlinem řešení).</li>
<li>Poslední sloupec je na zpětnou vazbu řešiteli, tedy (na rozdíl od Neveřejné poznámky, která je určena pro synchronizaci orgů) ji uvidí řešitelé. Změníte-li u nějakého hodnocení toto políčko, řešitel bude upozorněn emailem, pokud si tuto možnost nevypl ve svém profilu. Pohled řešitele si můžete prohlédnout <a href="{% url 'odevzdavatko_resitel_reseni' reseni.id %}">zde</a>. Pokud chcete z nějakého důvodu napsat řešitelům e-mail, klikněte na „Poslat mail všem řešitelům“.</li> <li>Poslední sloupec je na zpětnou vazbu řešiteli, tedy (na rozdíl od Neveřejné poznámky, která je určena pro synchronizaci orgů) ji uvidí řešitelé. Zatím jen pasivně (nechodí e-mail). Pohled řešitele si můžete prohlédnout <a href="{% url 'odevzdavatko_resitel_reseni' reseni.id %}">zde</a>. Pokud chcete z nějakého důvodu napsat řešitelům e-mail, klikněte na „Poslat mail všem řešitelům“.</li>
</ol> </ol>
Další poznámky Další poznámky

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('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/', 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>/', 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: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/<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())), 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.urls import reverse
from django.db import transaction from django.db import transaction
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponse
from dataclasses import dataclass from dataclasses import dataclass
import datetime import datetime
from decimal import Decimal from decimal import Decimal
from itertools import groupby from itertools import groupby
import logging import logging
import os
import tempfile
import zipfile
from . import forms as f from . import forms as f
from .forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm from .forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm
from .models import Hodnoceni, Reseni from .models import Hodnoceni, Reseni
from odevzdavatko.templatetags.jmena import jmeno_jako_prefix
from personalni.models import Resitel, Osoba, Organizator from personalni.models import Resitel, Osoba, Organizator
from tvorba.models import Problem, Deadline, Rocnik from tvorba.models import Problem, Deadline, Rocnik
from tvorba.utils import resi_v_rocniku 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. # 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 = 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) self.reseni = self.reseni.filter(cas_doruceni__date__gt=reseni_od, cas_doruceni__date__lte=reseni_do)
if jen_neobodovane: if jen_neobodovane:
self.reseni = self.reseni.filter(hodnoceni__body__isnull=True) 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() 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. # 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")) 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 return qs
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
@ -175,6 +186,47 @@ class TabulkaOdevzdanychReseniView(ListView):
return ctx 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? # Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji?
class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixin, View): class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixin, View):
"""Rozskok mezi více řešeními téhož problému od téhož řešitele. """Rozskok mezi více řešeními téhož problému od téhož řešitele.
@ -222,17 +274,6 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
ctx["problem_id"] = self.kwargs['problem'] ctx["problem_id"] = self.kwargs['problem']
return ctx return ctx
HODNOCENI_INITIAL_DATA = [
"problem",
"body",
"body_celkem",
"body_neprepocitane",
"body_neprepocitane_celkem",
"body_max",
"body_neprepocitane_max",
"deadline_body",
"feedback",
]
## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex ## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex
class DetailReseniView(DetailView): class DetailReseniView(DetailView):
""" Náhled na řešení. Editace je v :py:class:`EditReseniView`. """ """ Náhled na řešení. Editace je v :py:class:`EditReseniView`. """
@ -243,7 +284,18 @@ class DetailReseniView(DetailView):
self.reseni = get_object_or_404(Reseni, id=self.kwargs['pk']) self.reseni = get_object_or_404(Reseni, id=self.kwargs['pk'])
result = [] # Slovníky s klíči problem, body, deadline_body -- initial data pro f.OhodnoceniReseniFormSet result = [] # Slovníky s klíči problem, body, deadline_body -- initial data pro f.OhodnoceniReseniFormSet
for hodn in Hodnoceni.objects.filter(reseni=self.reseni): for hodn in Hodnoceni.objects.filter(reseni=self.reseni):
result.append({attr: getattr(hodn, attr) for attr in HODNOCENI_INITIAL_DATA}) seznam_atributu = [
"problem",
"body",
"body_celkem",
"body_neprepocitane",
"body_neprepocitane_celkem",
"body_max",
"body_neprepocitane_max",
"deadline_body",
"feedback",
]
result.append({attr: getattr(hodn, attr) for attr in seznam_atributu})
return result return result
def get_context_data(self, **kw): def get_context_data(self, **kw):
@ -291,9 +343,9 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
reseni = get_object_or_404(Reseni, pk=pk) reseni = get_object_or_404(Reseni, pk=pk)
success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk}) success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk})
formset = f.OhodnoceniReseniFormSet(request.POST, initial=[ # FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově
{k: getattr(h, k) for k in HODNOCENI_INITIAL_DATA} for h in Hodnoceni.objects.filter(reseni=reseni) # Also: https://docs.djangoproject.com/en/2.2/topics/forms/modelforms/#django.forms.ModelForm
]) formset = f.OhodnoceniReseniFormSet(request.POST)
poznamka_form = f.PoznamkaReseniForm(request.POST, instance=reseni) poznamka_form = f.PoznamkaReseniForm(request.POST, instance=reseni)
# TODO: Napsat validaci formuláře a formsetu # TODO: Napsat validaci formuláře a formsetu
if not (formset.is_valid() and poznamka_form.is_valid()): if not (formset.is_valid() and poznamka_form.is_valid()):
@ -309,9 +361,7 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
qs.delete() qs.delete()
# Vyrobíme nová podle formsetu # Vyrobíme nová podle formsetu
notifikace = False
for form in formset: for form in formset:
notifikace |= 'feedback' in form.changed_data
data_for_hodnoceni = form.cleaned_data data_for_hodnoceni = form.cleaned_data
data_for_body = data_for_hodnoceni.copy() data_for_body = data_for_hodnoceni.copy()
del(data_for_hodnoceni["body_celkem"]) del(data_for_hodnoceni["body_celkem"])
@ -322,44 +372,16 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
**form.cleaned_data, **form.cleaned_data,
) )
logger.info(f"Creating Hodnoceni: {hodnoceni}") logger.info(f"Creating Hodnoceni: {hodnoceni}")
# FIXME následující kód má velmi vysokou šanci se rozbít, vymyslet, jak to udělat jinak
zmeny_bodu = [it for it in form.changed_data if it.startswith("body")] zmeny_bodu = [it for it in form.changed_data if it.startswith("body")]
if len(zmeny_bodu) != 0: if len(zmeny_bodu) == 1:
body_nastaveny: None | tuple[str, object] = None hodnoceni.__setattr__(zmeny_bodu[0], data_for_body[zmeny_bodu[0]])
def nastav_body(jake, na_kolik): # > jedna změna je špatně, ale 4 "změny" znamenají že nebylo nic zadáno
nonlocal body_nastaveny if len(zmeny_bodu) > 1 and len(zmeny_bodu) != 4 and len(zmeny_bodu) != 2:
if body_nastaveny is not None: # 4 znamená vše už vyplněno a nic nezměněno, 2 znamená předvyplnili se součty a nic se nezměnilo
logger.warning(f"Hodnocení {hodnoceni} s id {hodnoceni.id} k řešení {reseni.id} mělo mít nastavené kromě {body_nastaveny[0]} na {body_nastaveny[1]} ještě další body: {jake} na {na_kolik}. Nastavuji -0.1.") logger.warning(f"Hodnocení {hodnoceni} mělo mít nastavené víc různých bodů: {zmeny_bodu}. Nastavuji -0.1.")
hodnoceni.body = -0.1 hodnoceni.body = -0.1
else:
body_nastaveny = (jake, na_kolik)
hodnoceni.__setattr__(jake, na_kolik)
for key, value in data_for_body.items():
if key.startswith("body") and value is not None:
nastav_body(key, value)
# Něco se změnilo, ale nic není nastavené = něco bylo smazáno
if body_nastaveny is None:
hodnoceni.body = None
hodnoceni.save() hodnoceni.save()
adresati = reseni.resitele.filter(upozornovat_na_opravy_reseni=True).values_list('osoba__email', flat=True)
if notifikace and adresati:
email = EmailMessage(
subject='Změna hodnocení odevzdaného řešení',
body=f"""Milá řešitelko, milý řešiteli,
došlo ke změně zpětné vazby k Tebou odevzdanému řešení. Zobrazit si ji můžeš na {reseni.resitel_url()}.
Tvoji organizátoři M&M
---
Nechceš-li tato upozornění dostávat, můžeš si to nastavit ve svém profilu.""",
from_email='odevzdavatko@mam.mff.cuni.cz',
bcc=adresati,
)
email.send()
return redirect(success_url) return redirect(success_url)

View file

@ -71,8 +71,6 @@ class UdajeForm(forms.Form):
zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat e-mailem upozornění na vydání nového čísla', required=False, initial=True) zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat e-mailem upozornění na vydání nového čísla', required=False, initial=True)
spam = forms.BooleanField(label='Souhlasím se zasíláním propagačních materiálů od MFF UK', required=False) spam = forms.BooleanField(label='Souhlasím se zasíláním propagačních materiálů od MFF UK', required=False)
upozornovat_na_opravy_reseni = forms.BooleanField(label='Chci dostávat emailová upozornění na změnu zpětné vazby k mým řešením', required=False, initial=True)
def clean_prezdivka_resitele(self): def clean_prezdivka_resitele(self):
prezdivka_resitele = self.cleaned_data.get('prezdivka_resitele') prezdivka_resitele = self.cleaned_data.get('prezdivka_resitele')
if prezdivka_resitele == '': if prezdivka_resitele == '':

View file

@ -1,22 +0,0 @@
# Generated by Django 4.2.16 on 2024-12-03 19:08
from django.db import migrations, models
def vypnuti_upozorneni_na_opravy_reseni(apps, schema_editor):
Resitel = apps.get_model('personalni', 'Resitel')
Resitel.objects.update(upozorneni=False)
class Migration(migrations.Migration):
dependencies = [
('personalni', '0017_odstrel_treenode_post'),
]
operations = [
migrations.AddField(
model_name='resitel',
name='upozorneni',
field=models.BooleanField(default=True, verbose_name='zasílat upozornění na změnu zpětné vazby k řešení emailem'),
),
migrations.RunPython(vypnuti_upozorneni_na_opravy_reseni),
]

View file

@ -1,18 +0,0 @@
# Generated by Django 4.2.18 on 2025-01-14 19:48
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('personalni', '0018_resitel_upozorneni'),
]
operations = [
migrations.RenameField(
model_name='resitel',
old_name='upozorneni',
new_name='upozornovat_na_opravy_reseni',
),
]

View file

@ -250,8 +250,6 @@ class Resitel(SeminarModelBase):
poznamka = models.TextField('neveřejná poznámka', blank=True, poznamka = models.TextField('neveřejná poznámka', blank=True,
help_text='Neveřejná poznámka k řešiteli (plain text)') help_text='Neveřejná poznámka k řešiteli (plain text)')
upozornovat_na_opravy_reseni = models.BooleanField('zasílat upozornění na změnu zpětné vazby k řešení emailem', default=True)
def export_row(self): def export_row(self):
"Slovnik pro pouziti v AESOP exportu" "Slovnik pro pouziti v AESOP exportu"

View file

@ -1,34 +0,0 @@
.seznam {
display: flex;
flex-direction: column;
gap: 0.3em;
}
.hint {
border: 1px solid #ccc;
padding: 0.3em 1em;
border-radius: 5px;
margin-bottom: 1em;
}
.osoba {
display: flex;
justify-content: space-between;
gap: 0.5em;
.uno {
flex: 2;
}
.dos {
flex: 2;
}
.tres {
flex: 1;
}
.grey {
opacity: 0.5;
}
}

View file

@ -1,28 +0,0 @@
{% extends "base.html" %}
{% block custom_css %}
{% load static %}
<link href="{% static 'personalni/jak_se_dozvedeli.css' %}" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="seznam">
<div class="osoba hint">
<div class="uno">Jméno</div>
<div class="dos">Jak se dozvěděli</div>
<div class="tres">Datum registrace</div>
</div>
{% for osoba in object_list %}
<div class="osoba">
<div class="uno">{{ osoba.jmeno }} {{ osoba.prijmeni }}</div>
<div class="dos {% if not osoba.jak_se_dozvedeli %}grey{% endif %}">{% if osoba.jak_se_dozvedeli %} {{osoba.jak_se_dozvedeli}} {% else %} NEZADÁNO {% endif %}</div>
<div class="tres">{{ osoba.datum_registrace }}</div>
</div>
{% endfor %}
</div>
{% endblock%}

View file

@ -51,7 +51,6 @@
</h4> </h4>
<table class="form"> <table class="form">
{% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_emailem %} {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_emailem %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.upozornovat_na_opravy_reseni %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_papirove %} {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_papirove %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.spam %} {% include "personalni/udaje/prihlaska_field.html" with field=form.spam %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat %} {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat %}

View file

@ -33,11 +33,4 @@ urlpatterns = [
name='stari_organizatori' name='stari_organizatori'
), ),
# Zpřístupnění dat z "jak jste se o nás dozvěděli" pro orgy propagace
path(
'org/propagace/jak-se-dozvedeli/',
org_required(views.JakSeDozvedeliView.as_view()),
name='jak_se_dozvedeli'
)
] ]

View file

@ -34,7 +34,7 @@ from various.autentizace.utils import posli_reset_hesla
from django.forms.models import model_to_dict from django.forms.models import model_to_dict
from .models import Organizator, Osoba from .models import Organizator
def aktivniOrganizatori(datum=timezone.now()): def aktivniOrganizatori(datum=timezone.now()):
@ -62,11 +62,6 @@ class CojemamOrganizatoriStariView(generic.ListView):
id__in=aktivniOrganizatori() id__in=aktivniOrganizatori()
).order_by('-organizuje_do') ).order_by('-organizuje_do')
class JakSeDozvedeliView(generic.ListView):
model = Osoba
template_name = 'personalni/jak_se_dozvedeli.html'
queryset = Osoba.objects.order_by('-datum_registrace')
def obalkyView(request, resitele): def obalkyView(request, resitele):
if len(resitele) == 0: if len(resitele) == 0:
@ -235,7 +230,6 @@ def resitelEditView(request):
resitel_edit.zasilat = fcd['zasilat'] resitel_edit.zasilat = fcd['zasilat']
resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
resitel_edit.zasilat_cislo_papirove = fcd['zasilat_cislo_papirove'] resitel_edit.zasilat_cislo_papirove = fcd['zasilat_cislo_papirove']
resitel_edit.upozornovat_na_opravy_reseni = fcd['upozornovat_na_opravy_reseni']
if fcd.get('skola'): if fcd.get('skola'):
resitel_edit.skola = fcd['skola'] resitel_edit.skola = fcd['skola']
else: else:

View file

@ -30,8 +30,6 @@ Jak moc by ses chtěl(a) zúčastnit následujících přednášek?
<INPUT TYPE="radio" NAME="q{{p.pk}}" VALUE="1" {% if h == 1 %} CHECKED="checked" {% endif %}> rozhodně chci <INPUT TYPE="radio" NAME="q{{p.pk}}" VALUE="1" {% if h == 1 %} CHECKED="checked" {% endif %}> rozhodně chci
</td></tr> </td></tr>
<tr><td>&nbsp;</td></tr> <tr><td>&nbsp;</td></tr>
{% empty %}
Nejsou žádné přednášky o kterých by šlo hlasovat.
{% endfor %} {% endfor %}
<tr><td><input name="odeslat" type="submit" value="Odeslat"></td><tr> <tr><td><input name="odeslat" type="submit" value="Odeslat"></td><tr>
</table> </table>

View file

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load humanize %}
{% load static %}
{% block content %}
<h1> Děkujeme. </h1>
{% endblock %}

View file

@ -1,5 +1,3 @@
import http
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.views import generic from django.views import generic
from django.shortcuts import HttpResponseRedirect from django.shortcuts import HttpResponseRedirect
@ -7,22 +5,14 @@ from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Sum from django.db.models import Sum
from django.forms import Form from django.forms import Form
from various.views.pomocne import formularOKView
from various.models import Nastaveni
from prednasky.models import Prednaska, Hlasovani, Seznam, STAV_NAVRH from prednasky.models import Prednaska, Hlasovani, Seznam, STAV_NAVRH
from soustredeni.models import Soustredeni from soustredeni.models import Soustredeni
from personalni.models import Osoba from personalni.models import Osoba
def newPrednaska(request): def newPrednaska(request):
# hlasovani se vztahuje k nejnovejsimu soustredeni # hlasovani se vztahuje k nejnovejsimu soustredeni
sous = Nastaveni.get_solo().aktualni_sous sous = Soustredeni.objects.first()
seznam = Seznam.objects.filter(soustredeni = sous, stav = STAV_NAVRH).first() seznam = Seznam.objects.filter(soustredeni = sous, stav = STAV_NAVRH).first()
if sous is None or seznam is None:
return render(request, 'universal.html', {
'title': "Nelze hlasovat",
'text': "Není žádný seznam přednášek, o kterém by se dalo hlasovat.",
}, status=http.HTTPStatus.NOT_FOUND)
osoba = Osoba.objects.filter(user=request.user).first() osoba = Osoba.objects.filter(user=request.user).first()
ucastnik = osoba.plne_jmeno() + ' ' + str(osoba.id) ucastnik = osoba.plne_jmeno() + ' ' + str(osoba.id)
# obsluha formulare # obsluha formulare
@ -64,7 +54,7 @@ def newPrednaska(request):
def Prednaska_hotovo(request): def Prednaska_hotovo(request):
return formularOKView(request, "Děkujeme za vyplnění hlasování o přednáškách a těšíme se na soustředění.") return render(request, 'prednasky/hotovo.html')
class MetaSeznamListView(generic.ListView): class MetaSeznamListView(generic.ListView):
model = Seznam model = Seznam

View file

@ -17,7 +17,7 @@ django-solo # Singleton model (speciálně Nastavení)
django-ckeditor-5 # Editor htmlka (hlavně v adminu u flatpages) django-ckeditor-5 # Editor htmlka (hlavně v adminu u flatpages)
django-cleanup # Uklízí media/ od smazaných „databázových“ souborů django-cleanup # Uklízí media/ od smazaných „databázových“ souborů
django-taggit # Taggy v djangu (speciálně zaměření problémů) django-taggit # Taggy v djangu (speciálně zaměření problémů)
django-autocomplete-light>=3.9.0,<3.12.0 # Automatické doplňování (problémů, účastníků, …) ve formulářích django-autocomplete-light>=3.9.0 # Automatické doplňování (problémů, účastníků, …) ve formulářích
django-imagekit # Všechny možné obrázky v Djangu django-imagekit # Všechny možné obrázky v Djangu
django-polymorphic # Polymorfismus na django modelech (hlavně Problém nebo treenode) django-polymorphic # Polymorfismus na django modelech (hlavně Problém nebo treenode)
django-sitetree # Struktura stránek, hlavně pro meníčko django-sitetree # Struktura stránek, hlavně pro meníčko

View file

@ -1,27 +0,0 @@
# Generated by Django 4.2.16 on 2025-01-21 20:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('personalni', '0019_rename_upozorneni_resitel_upozornovat_na_opravy_reseni'),
('tvorba', '0007_alter_deadline_typ'),
]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=[],
state_operations=[
migrations.AlterField(
model_name='problem',
name='opravovatele',
field=models.ManyToManyField(blank=True, db_table='seminar_problemy_opravovatele', related_name='opravovatele_%(class)s', to='personalni.organizator', verbose_name='opravovatelé'),
),
migrations.DeleteModel(
name='Problemy_Opravovatele',
),
]
),
]

View file

@ -393,6 +393,20 @@ class ZmrazenaVysledkovka(SeminarModelBase):
html = models.TextField(null=False, blank=False) html = models.TextField(null=False, blank=False)
class Problemy_Opravovatele(SeminarModelBase):
"""Jen vazebná tabulka pro opravovatele.
Ona stejně existovala, při přesunu mezi aplikacemi jen potřebujeme zajistit nepřejmenování DB tabulky.
Proto taky nepotřebuje žádná specifika, ze :py:class:SeminarModelBase: dědí ze zvyku než že by to k něčemu kdy měo být.
"""
class Meta:
db_table = 'seminar_problemy_opravovatele'
id = models.AutoField(primary_key = True)
problem = models.ForeignKey('Problem', on_delete=models.CASCADE)
organizator = models.ForeignKey(Organizator, on_delete=models.CASCADE)
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
# Pozor na následující řádek. *Nekrmit, asi kouše!* # Pozor na následující řádek. *Nekrmit, asi kouše!*
class Problem(SeminarModelBase,PolymorphicModel): class Problem(SeminarModelBase,PolymorphicModel):
@ -448,7 +462,7 @@ class Problem(SeminarModelBase,PolymorphicModel):
on_delete=models.SET_NULL) on_delete=models.SET_NULL)
opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé', opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé',
blank=True, related_name='opravovatele_%(class)s', db_table='seminar_problemy_opravovatele') blank=True, related_name='opravovatele_%(class)s', through=Problemy_Opravovatele)
kod = models.CharField('lokální kód', max_length=32, blank=True, default='', kod = models.CharField('lokální kód', max_length=32, blank=True, default='',
help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku') help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku')

View file

@ -1,20 +0,0 @@
# Generated by Django 4.2.16 on 2025-01-21 20:34
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('soustredeni', '0013_alter_soustredeni_kontaktnicek_pdf_and_more'),
('various', '0006_tvorba_post'),
]
operations = [
migrations.AddField(
model_name='nastaveni',
name='aktualni_sous',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='soustredeni.soustredeni', verbose_name='Aktuálně připravovaný sous'),
),
]

View file

@ -26,11 +26,6 @@ class Nastaveni(SingletonModel):
verbose_name="Účastnický poplatek za soustředění", verbose_name="Účastnický poplatek za soustředění",
default=1000) default=1000)
aktualni_sous = models.ForeignKey(
"soustredeni.Soustredeni", verbose_name='Aktuálně připravovaný sous',
null=True, blank=True, on_delete=models.PROTECT,
)
@property @property
def aktualni_rocnik(self): def aktualni_rocnik(self):
return self.aktualni_cislo.rocnik return self.aktualni_cislo.rocnik