odevzdavatko: odesílání emailu řešiteli při změně zpětné vazby #83

Open
karelb wants to merge 1 commits from notifikace-zpetne-vazby into master
  1. 3
      odevzdavatko/models.py
  2. 2
      odevzdavatko/templates/odevzdavatko/detail.html
  3. 48
      odevzdavatko/views.py
  4. 2
      personalni/forms.py
  5. 18
      personalni/migrations/0018_resitel_upozorneni.py
  6. 2
      personalni/models.py
  7. 1
      personalni/templates/personalni/udaje/udaje.html
  8. 1
      personalni/views.py

3
odevzdavatko/models.py

@ -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

2
odevzdavatko/templates/odevzdavatko/detail.html

@ -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

48
odevzdavatko/views.py

@ -222,17 +222,7 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
ctx["problem_id"] = self.kwargs['problem'] ctx["problem_id"] = self.kwargs['problem']
return ctx return ctx
## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex HODNOCENI_INITIAL_DATA = [
class DetailReseniView(DetailView):
""" Náhled na řešení. Editace je v :py:class:`EditReseniView`. """
model = Reseni
template_name = 'odevzdavatko/detail.html'
def aktualni_hodnoceni(self):
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
for hodn in Hodnoceni.objects.filter(reseni=self.reseni):
seznam_atributu = [
"problem", "problem",
"body", "body",
"body_celkem", "body_celkem",
@ -243,7 +233,17 @@ class DetailReseniView(DetailView):
"deadline_body", "deadline_body",
"feedback", "feedback",
] ]
result.append({attr: getattr(hodn, attr) for attr in seznam_atributu}) ## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex
class DetailReseniView(DetailView):
""" Náhled na řešení. Editace je v :py:class:`EditReseniView`. """
model = Reseni
template_name = 'odevzdavatko/detail.html'
def aktualni_hodnoceni(self):
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
for hodn in Hodnoceni.objects.filter(reseni=self.reseni):
result.append({attr: getattr(hodn, attr) for attr in HODNOCENI_INITIAL_DATA})
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)

2
personalni/forms.py

@ -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

@ -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'),
),
]

2
personalni/models.py

@ -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"

1
personalni/templates/personalni/udaje/udaje.html

@ -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 %}

1
personalni/views.py

@ -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…
Cancel
Save