odevzdavatko: odesílání emailu řešiteli při změně zpětné vazby
Toto se rozbíjí, když dojde ke smazání hodnocení v pořadí dříve, než nějaké hodnocení s neprázdnou zpětnou vazbou, neboť řádky formsetu jsou přečíslovány a pak špatně spárovány s původními hodnotami, takže se nesprávně detekuje změna.
This commit is contained in:
parent
9020f5551d
commit
6ea212cdf8
8 changed files with 61 additions and 16 deletions
|
@ -65,6 +65,9 @@ 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
|
||||||
|
|
||||||
|
|
|
@ -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é. 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>
|
<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>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
Další poznámky
|
Další poznámky
|
||||||
|
|
|
@ -222,6 +222,17 @@ 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`. """
|
||||||
|
@ -232,18 +243,7 @@ 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):
|
||||||
seznam_atributu = [
|
result.append({attr: getattr(hodn, attr) for attr in HODNOCENI_INITIAL_DATA})
|
||||||
"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 +291,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})
|
||||||
|
|
||||||
# FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově
|
formset = f.OhodnoceniReseniFormSet(request.POST, initial=[
|
||||||
# Also: https://docs.djangoproject.com/en/2.2/topics/forms/modelforms/#django.forms.ModelForm
|
{k: getattr(h, k) for k in HODNOCENI_INITIAL_DATA} for h in Hodnoceni.objects.filter(reseni=reseni)
|
||||||
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,7 +309,9 @@ 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"])
|
||||||
|
@ -330,6 +332,22 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
|
||||||
hodnoceni.body = -0.1
|
hodnoceni.body = -0.1
|
||||||
hodnoceni.save()
|
hodnoceni.save()
|
||||||
|
|
||||||
|
adresati = reseni.resitele.filter(upozorneni=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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,8 @@ 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)
|
||||||
|
|
||||||
|
upozorneni = 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 == '':
|
||||||
|
|
18
personalni/migrations/0018_resitel_upozorneni.py
Normal file
18
personalni/migrations/0018_resitel_upozorneni.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.2.16 on 2024-12-03 19:08
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -250,6 +250,8 @@ 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)')
|
||||||
|
|
||||||
|
upozorneni = 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"
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
</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.upozorneni %}
|
||||||
{% 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 %}
|
||||||
|
|
|
@ -230,6 +230,7 @@ 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.upozorneni = fcd['upozorneni']
|
||||||
if fcd.get('skola'):
|
if fcd.get('skola'):
|
||||||
resitel_edit.skola = fcd['skola']
|
resitel_edit.skola = fcd['skola']
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in a new issue