diff --git a/odevzdavatko/models.py b/odevzdavatko/models.py index 3b510fe7..6666fcb1 100644 --- a/odevzdavatko/models.py +++ b/odevzdavatko/models.py @@ -65,6 +65,9 @@ class Reseni(SeminarModelBase): def absolute_url(self): 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: # Konfera diff --git a/odevzdavatko/templates/odevzdavatko/detail.html b/odevzdavatko/templates/odevzdavatko/detail.html index 616ec8e0..4d2b780a 100644 --- a/odevzdavatko/templates/odevzdavatko/detail.html +++ b/odevzdavatko/templates/odevzdavatko/detail.html @@ -191,7 +191,7 @@ Sloupce:
  • 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í).
  • -
  • 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 zde. Pokud chcete z nějakého důvodu napsat řešitelům e-mail, klikněte na „Poslat mail všem řešitelům“.
  • +
  • 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 zde. Pokud chcete z nějakého důvodu napsat řešitelům e-mail, klikněte na „Poslat mail všem řešitelům“.
  • Další poznámky diff --git a/odevzdavatko/views.py b/odevzdavatko/views.py index 2a213a2c..b5c3d54d 100644 --- a/odevzdavatko/views.py +++ b/odevzdavatko/views.py @@ -222,6 +222,17 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi ctx["problem_id"] = self.kwargs['problem'] 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 class DetailReseniView(DetailView): """ 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']) 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", - "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}) + result.append({attr: getattr(hodn, attr) for attr in HODNOCENI_INITIAL_DATA}) return result def get_context_data(self, **kw): @@ -291,9 +291,9 @@ def hodnoceniReseniView(request, pk, *args, **kwargs): reseni = get_object_or_404(Reseni, 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ě - # Also: https://docs.djangoproject.com/en/2.2/topics/forms/modelforms/#django.forms.ModelForm - formset = f.OhodnoceniReseniFormSet(request.POST) + formset = f.OhodnoceniReseniFormSet(request.POST, initial=[ + {k: getattr(h, k) for k in HODNOCENI_INITIAL_DATA} for h in Hodnoceni.objects.filter(reseni=reseni) + ]) poznamka_form = f.PoznamkaReseniForm(request.POST, instance=reseni) # TODO: Napsat validaci formuláře a formsetu if not (formset.is_valid() and poznamka_form.is_valid()): @@ -309,7 +309,9 @@ def hodnoceniReseniView(request, pk, *args, **kwargs): qs.delete() # Vyrobíme nová podle formsetu + notifikace = False for form in formset: + notifikace |= 'feedback' in form.changed_data data_for_hodnoceni = form.cleaned_data data_for_body = data_for_hodnoceni.copy() del(data_for_hodnoceni["body_celkem"]) @@ -330,6 +332,22 @@ def hodnoceniReseniView(request, pk, *args, **kwargs): hodnoceni.body = -0.1 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) diff --git a/personalni/forms.py b/personalni/forms.py index ae08a8c9..2f6c79c8 100644 --- a/personalni/forms.py +++ b/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) 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): prezdivka_resitele = self.cleaned_data.get('prezdivka_resitele') if prezdivka_resitele == '': diff --git a/personalni/migrations/0018_resitel_upozorneni.py b/personalni/migrations/0018_resitel_upozorneni.py new file mode 100644 index 00000000..64aede5f --- /dev/null +++ b/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'), + ), + ] diff --git a/personalni/models.py b/personalni/models.py index 636b132e..930716a7 100644 --- a/personalni/models.py +++ b/personalni/models.py @@ -250,6 +250,8 @@ class Resitel(SeminarModelBase): poznamka = models.TextField('neveřejná poznámka', blank=True, 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): "Slovnik pro pouziti v AESOP exportu" diff --git a/personalni/templates/personalni/udaje/udaje.html b/personalni/templates/personalni/udaje/udaje.html index 894ddaf9..5ed30b74 100644 --- a/personalni/templates/personalni/udaje/udaje.html +++ b/personalni/templates/personalni/udaje/udaje.html @@ -51,6 +51,7 @@ {% 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.spam %} {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat %} diff --git a/personalni/views.py b/personalni/views.py index 7c95325c..38f9bc57 100644 --- a/personalni/views.py +++ b/personalni/views.py @@ -230,6 +230,7 @@ def resitelEditView(request): resitel_edit.zasilat = fcd['zasilat'] resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] resitel_edit.zasilat_cislo_papirove = fcd['zasilat_cislo_papirove'] + resitel_edit.upozorneni = fcd['upozorneni'] if fcd.get('skola'): resitel_edit.skola = fcd['skola'] else: