Merge pull request 'odevzdavatko: odesílání emailu řešiteli při změně zpětné vazby' (!83) from notifikace-zpetne-vazby into master
Reviewed-on: #83
This commit is contained in:
		
						commit
						833893f233
					
				
					 9 changed files with 83 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,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(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) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
| 
 | 
 | ||||||
|  | 	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 == '': | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								personalni/migrations/0018_resitel_upozorneni.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								personalni/migrations/0018_resitel_upozorneni.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | # 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), | ||||||
|  |     ] | ||||||
|  | @ -0,0 +1,18 @@ | ||||||
|  | # 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', | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -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)') | ||||||
| 
 | 
 | ||||||
|  | 	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" | ||||||
|  |  | ||||||
|  | @ -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.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 %} | ||||||
|  |  | ||||||
|  | @ -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.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: | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue