Submitovatko: pomerne funkcni verze.
This commit is contained in:
		
							parent
							
								
									2583ea117a
								
							
						
					
					
						commit
						62b0b4a62f
					
				
					 9 changed files with 240 additions and 15 deletions
				
			
		|  | @ -2,6 +2,7 @@ from django import forms | ||||||
| from dal import autocomplete | from dal import autocomplete | ||||||
| from django.core.exceptions import ObjectDoesNotExist | from django.core.exceptions import ObjectDoesNotExist | ||||||
| from django.contrib.auth.models import User | from django.contrib.auth.models import User | ||||||
|  | from django.forms.models import inlineformset_factory | ||||||
| 
 | 
 | ||||||
| from .models import Skola, Resitel, Osoba, Problem | from .models import Skola, Resitel, Osoba, Problem | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
|  | @ -252,6 +253,25 @@ class VlozReseniForm(forms.Form): | ||||||
| 		super().__init__(*args, **kwargs) | 		super().__init__(*args, **kwargs) | ||||||
| 		#self.fields['favorite_color'] = forms.ChoiceField(choices=[(color.id, color.name) for color in Resitel.objects.all()]) | 		#self.fields['favorite_color'] = forms.ChoiceField(choices=[(color.id, color.name) for color in Resitel.objects.all()]) | ||||||
| 
 | 
 | ||||||
|  | class NahrajReseniForm(forms.ModelForm): | ||||||
|  | 	class Meta: | ||||||
|  | 		model = m.Reseni | ||||||
|  | 		fields = ('problem',) | ||||||
| 		 | 		 | ||||||
|  | 		widgets = {'problem': | ||||||
|  | 				autocomplete.ModelSelect2Multiple( | ||||||
|  | 					url='autocomplete_problem_odevzdatelny', | ||||||
|  | 					attrs = {'data-placeholder--id': '-1', | ||||||
|  | 						'data-placeholder--text' : '---', | ||||||
|  | 						'data-allow-clear': 'true'}, | ||||||
|  | 				) | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
|  | ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni,  | ||||||
|  | 		form = NahrajReseniForm, | ||||||
|  | 		fields = ('soubor','res_poznamka'), | ||||||
|  | 		widgets = {'res_poznamka':forms.TextInput()}, | ||||||
|  | 		extra = 1, | ||||||
|  | 
 | ||||||
|  | 		) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										24
									
								
								seminar/migrations/0074_auto_20200228_1401.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								seminar/migrations/0074_auto_20200228_1401.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | ||||||
|  | # Generated by Django 2.2.9 on 2020-02-28 13:01 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('seminar', '0073_copy_osoba_email_to_user_email'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='prilohareseni', | ||||||
|  |             name='res_poznamka', | ||||||
|  |             field=models.TextField(blank=True, help_text='Poznámka k příloze řešení, např. co daný soubor obsahuje', verbose_name='poznámka řešitele'), | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='hodnoceni', | ||||||
|  |             name='problem', | ||||||
|  |             field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='seminar.Problem', verbose_name='problém'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										18
									
								
								seminar/migrations/0075_auto_20200228_2010.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								seminar/migrations/0075_auto_20200228_2010.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | # Generated by Django 2.2.9 on 2020-02-28 19:10 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('seminar', '0074_auto_20200228_1401'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='hodnoceni', | ||||||
|  |             name='body', | ||||||
|  |             field=models.DecimalField(decimal_places=1, max_digits=8, null=True, verbose_name='body'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										19
									
								
								seminar/migrations/0076_auto_20200228_2013.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								seminar/migrations/0076_auto_20200228_2013.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | # Generated by Django 2.2.9 on 2020-02-28 19:13 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('seminar', '0075_auto_20200228_2010'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='hodnoceni', | ||||||
|  |             name='cislo_body', | ||||||
|  |             field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='seminar.Cislo', verbose_name='číslo pro body'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -920,10 +920,10 @@ class Hodnoceni(SeminarModelBase): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 	body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='body',  | 	body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='body',  | ||||||
| 		blank=False, null=False) | 		blank=False, null=True) | ||||||
| 
 | 
 | ||||||
| 	cislo_body = models.ForeignKey(Cislo, verbose_name='číslo pro body',  | 	cislo_body = models.ForeignKey(Cislo, verbose_name='číslo pro body',  | ||||||
| 		related_name='hodnoceni', blank=False, null=False, on_delete=models.PROTECT) | 		related_name='hodnoceni', blank=False, null=True, on_delete=models.PROTECT) | ||||||
| 
 | 
 | ||||||
| 	reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) | 	reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) | ||||||
| 
 | 
 | ||||||
|  | @ -935,17 +935,16 @@ class Hodnoceni(SeminarModelBase): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ## FIXME: Budeme řešit později, pokud to bude potřeba. | def aux_generate_filename(self, filename): | ||||||
| #def aux_generate_filename(self, filename): | 	"""Pomocná funkce generující ošetřený název souboru v adresáři s datem""" | ||||||
| #	"""Pomocná funkce generující ošetřený název souboru v adresáři s datem""" | 	clean = get_valid_filename( | ||||||
| #	clean = get_valid_filename( | 		unidecode(filename.replace('/', '-').replace('\0', '')) | ||||||
| #		unidecode(filename.replace('/', '-').replace('\0', '')) | 	) | ||||||
| #	) | 	datedir = timezone.now().strftime('%Y-%m') | ||||||
| #	datedir = timezone.now().strftime('%Y-%m') | 	fname = "{}_{}".format( | ||||||
| #	fname = "%s_%s" % ( | 		timezone.now().strftime('%Y-%m-%d-%H:%M'), | ||||||
| #		timezone.now().strftime('%Y-%m-%d-%H:%M'), | 		clean) | ||||||
| #		clean) | 	return os.path.join(datedir, fname) | ||||||
| #	return os.path.join(datedir, fname) |  | ||||||
| 
 | 
 | ||||||
| # Django neumí jednoduše serializovat partial nebo třídu s __call__ | # Django neumí jednoduše serializovat partial nebo třídu s __call__ | ||||||
| # (https://docs.djangoproject.com/en/1.8/topics/migrations/), | # (https://docs.djangoproject.com/en/1.8/topics/migrations/), | ||||||
|  | @ -989,8 +988,11 @@ class PrilohaReseni(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 příloze řešení (plain text), např. o původu') | 		help_text='Neveřejná poznámka k příloze řešení (plain text), např. o původu') | ||||||
| 	 | 	 | ||||||
|  | 	res_poznamka = models.TextField('poznámka řešitele', blank=True, | ||||||
|  | 		help_text='Poznámka k příloze řešení, např. co daný soubor obsahuje') | ||||||
|  | 
 | ||||||
| 	def __str__(self): | 	def __str__(self): | ||||||
| 		return self.soubor | 		return str(self.soubor) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Pohadka(SeminarModelBase): | class Pohadka(SeminarModelBase): | ||||||
|  |  | ||||||
							
								
								
									
										49
									
								
								seminar/static/seminar/dynamic_formsets.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								seminar/static/seminar/dynamic_formsets.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | ||||||
|  | // Credit https://medium.com/all-about-django/adding-forms-dynamically-to-a-django-formset-375f1090c2b0
 | ||||||
|  | function updateElementIndex(el, prefix, ndx) { | ||||||
|  | 	var id_regex = new RegExp('(' + prefix + '-\\d+)'); | ||||||
|  | 	var replacement = prefix + '-' + ndx; | ||||||
|  | 	if ($(el).attr("for")) { | ||||||
|  | 		$(el).attr("for", $(el).attr("for").replace(id_regex, replacement)); | ||||||
|  | 	} | ||||||
|  | 	if (el.id) { | ||||||
|  | 		el.id = el.id.replace(id_regex, replacement); | ||||||
|  | 	} | ||||||
|  | 	if (el.name) { | ||||||
|  | 		el.name = el.name.replace(id_regex, replacement); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Credit https://medium.com/all-about-django/adding-forms-dynamically-to-a-django-formset-375f1090c2b0
 | ||||||
|  | function deleteForm(prefix, btn) { | ||||||
|  |     var total = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val()); | ||||||
|  |     if (total >= 1){ | ||||||
|  |         btn.closest('div').remove(); | ||||||
|  |         var forms = $('.attachment'); | ||||||
|  |         $('#id_' + prefix + '-TOTAL_FORMS').val(forms.length); | ||||||
|  |         for (var i=0, formCount=forms.length; i<formCount; i++) { | ||||||
|  |             $(forms.get(i)).find(':input').each(function() { | ||||||
|  |                 updateElementIndex(this, prefix, i); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Credit: https://simpleit.rocks/python/django/dynamic-add-form-with-add-button-in-django-modelformset-template/
 | ||||||
|  | $(document).ready(function(){ | ||||||
|  | 	$('#add_attachment').click(function() { | ||||||
|  | 		var form_idx = $('#id_prilohy-TOTAL_FORMS').val(); | ||||||
|  | 		var new_form = $('#empty_form').html().replace(/__prefix__/g, form_idx); | ||||||
|  | 		$('#form_set').append(new_form); | ||||||
|  | 		// Newly created form has not the binding between remove button and remove function
 | ||||||
|  | 		// We need to add it manually
 | ||||||
|  | 		$('.remove_attachment').click(function(){ | ||||||
|  | 			deleteForm("prilohy",this); | ||||||
|  | 		}); | ||||||
|  | 		$('#id_prilohy-TOTAL_FORMS').val(parseInt(form_idx) + 1); | ||||||
|  | 	}); | ||||||
|  | 	$('.remove_attachment').click(function(){ | ||||||
|  | 		deleteForm("prilohy",this); | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
							
								
								
									
										44
									
								
								seminar/templates/seminar/nahraj_reseni.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								seminar/templates/seminar/nahraj_reseni.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,44 @@ | ||||||
|  | {% extends "seminar/zadani/base.html" %} | ||||||
|  | {% load staticfiles %} | ||||||
|  | {% block script %} | ||||||
|  |     <!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!--> | ||||||
|  |     {{form.media}} | ||||||
|  |     <script src="{% static 'seminar/dynamic_formsets.js' %}"></script> | ||||||
|  | {% endblock %} | ||||||
|  | 
 | ||||||
|  | {% block content %} | ||||||
|  | <h1> | ||||||
|  |   {% block nadpis1a %}{% block nadpis1b %} | ||||||
|  |     Vložit řešení | ||||||
|  |   {% endblock %}{% endblock %} | ||||||
|  | </h1> | ||||||
|  | <form enctype="multipart/form-data" action="{% url 'seminar_nahraj_reseni' %}" method="post"> | ||||||
|  |   {% csrf_token %} | ||||||
|  | {{form}} | ||||||
|  | {{prilohy.management_form}} | ||||||
|  | <div id="form_set"> | ||||||
|  | {% for form in prilohy.forms %} | ||||||
|  | 	<div class="attachment"> | ||||||
|  | 	{{form.non_field_errors}} | ||||||
|  | 	{{form.errors}} | ||||||
|  |         <table class='no_error'> | ||||||
|  |             {{ form }} | ||||||
|  |         </table> | ||||||
|  | 	<input type="button" value="Odebrat" class="remove_attachment" id="{{form.prefix}}-jsremove"> | ||||||
|  | 	</div> | ||||||
|  | {% endfor %} | ||||||
|  | </div> | ||||||
|  | <input type="button" value="Přidat přílohu" id="add_attachment"> | ||||||
|  | <div id="empty_form" style="display:none"> | ||||||
|  | 	<div class="attachment"> | ||||||
|  |         <table class='no_error'> | ||||||
|  |             {{ prilohy.empty_form }} | ||||||
|  |         </table> | ||||||
|  | 	<input type="button" value="Odebrat" class="remove_attachment" id="id_prilohy-__prefix__-jsremove"> | ||||||
|  | 	</div> | ||||||
|  | 
 | ||||||
|  | </div> | ||||||
|  |     <input type="submit" value="Odevzdat"> | ||||||
|  | </form> | ||||||
|  | 
 | ||||||
|  | {% endblock %} | ||||||
|  | @ -109,6 +109,7 @@ urlpatterns = [ | ||||||
| 	path('auth/resitel/', views.ResitelView.as_view(), name='seminar_resitel'), | 	path('auth/resitel/', views.ResitelView.as_view(), name='seminar_resitel'), | ||||||
| 	path('autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), | 	path('autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), | ||||||
| 	path('autocomplete/resitel/',views.ResitelAutocomplete.as_view(), name='autocomplete_resitel'), | 	path('autocomplete/resitel/',views.ResitelAutocomplete.as_view(), name='autocomplete_resitel'), | ||||||
|  | 	path('autocomplete/problem/odevzdatelny',views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'), | ||||||
| 	path('auth/reset_password/', views.PasswordResetView.as_view(), name='reset_password'), | 	path('auth/reset_password/', views.PasswordResetView.as_view(), name='reset_password'), | ||||||
| 	path('auth/change_password/', views.PasswordChangeView.as_view(), name='change_password'), | 	path('auth/change_password/', views.PasswordChangeView.as_view(), name='change_password'), | ||||||
| 	path('auth/reset_password_done/', views.PasswordResetDoneView.as_view(), name='reset_password_done'), | 	path('auth/reset_password_done/', views.PasswordResetDoneView.as_view(), name='reset_password_done'), | ||||||
|  | @ -118,6 +119,7 @@ urlpatterns = [ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 	path('temp/add_solution', views.AddSolutionView.as_view(),name='seminar_vloz_reseni'), | 	path('temp/add_solution', views.AddSolutionView.as_view(),name='seminar_vloz_reseni'), | ||||||
|  | 	path('temp/submit_solution', views.SubmitSolutionView.as_view(),name='seminar_nahraj_reseni'), | ||||||
| 
 | 
 | ||||||
| 	path('', views.TitulniStranaView.as_view(), name='titulni_strana'), | 	path('', views.TitulniStranaView.as_view(), name='titulni_strana'), | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ from django.utils.translation import ugettext as _ | ||||||
| from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect | from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect | ||||||
| from django.db.models import Q, Sum, Count | from django.db.models import Q, Sum, Count | ||||||
| from django.views.decorators.csrf import ensure_csrf_cookie | from django.views.decorators.csrf import ensure_csrf_cookie | ||||||
| from django.views.generic.edit import FormView | from django.views.generic.edit import FormView, CreateView | ||||||
| from django.contrib.auth import authenticate, login, get_user_model, logout | from django.contrib.auth import authenticate, login, get_user_model, logout | ||||||
| from django.contrib.auth import views as auth_views | from django.contrib.auth import views as auth_views | ||||||
| from django.contrib.auth.models import User | from django.contrib.auth.models import User | ||||||
|  | @ -1157,6 +1157,40 @@ class AddSolutionView(LoginRequiredMixin, FormView): | ||||||
| 	form_class = f.VlozReseniForm | 	form_class = f.VlozReseniForm | ||||||
| 	success_url = '/' | 	success_url = '/' | ||||||
| 
 | 
 | ||||||
|  | class SubmitSolutionView(LoginRequiredMixin, CreateView): | ||||||
|  | 	model = s.Reseni | ||||||
|  | 	template_name = 'seminar/nahraj_reseni.html' | ||||||
|  | 	form_class = f.NahrajReseniForm | ||||||
|  | 
 | ||||||
|  | 	def get_context_data(self,**kwargs): | ||||||
|  | 		data = super().get_context_data(**kwargs) | ||||||
|  | 		if self.request.POST: | ||||||
|  | 			data['prilohy'] = f.ReseniSPrilohamiFormSet(self.request.POST,self.request.FILES)  | ||||||
|  | 		else: | ||||||
|  | 			data['prilohy'] = f.ReseniSPrilohamiFormSet()  | ||||||
|  | 		return data | ||||||
|  | 
 | ||||||
|  | 	# FIXME prepsat tak, aby form_valid se volalo jen tehdy, kdyz je form i formset validni | ||||||
|  | 	# Inspirace: https://stackoverflow.com/questions/41599809/using-a-django-filefield-in-an-inline-formset | ||||||
|  | 	def form_valid(self,form): | ||||||
|  | 		context = self.get_context_data() | ||||||
|  | 		prilohy = context['prilohy'] | ||||||
|  | 		with transaction.atomic(): | ||||||
|  | 			self.object = form.save() | ||||||
|  | 			self.object.resitele.add(Resitel.objects.get(osoba__user = self.request.user)) | ||||||
|  | 			self.object.cas_doruceni = datetime.now()  | ||||||
|  | 			self.object.forma = s.Reseni.FORMA_UPLOAD | ||||||
|  | 			if prilohy.is_valid(): | ||||||
|  | 				prilohy.instance = self.object | ||||||
|  | 				prilohy.save() | ||||||
|  | 			else: | ||||||
|  | 				raise Exception("Uploadovane soubory nebyly validni") | ||||||
|  | 
 | ||||||
|  | 		return HttpResponseRedirect(self.get_success_url()) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def resetPasswordView(request): | def resetPasswordView(request): | ||||||
| 	pass | 	pass | ||||||
| 
 | 
 | ||||||
|  | @ -1349,6 +1383,19 @@ class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetVie | ||||||
| 				) | 				) | ||||||
| 		return qs | 		return qs | ||||||
| 
 | 
 | ||||||
|  | class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView): | ||||||
|  | 	def get_queryset(self): | ||||||
|  | 		nastaveni = get_object_or_404(Nastaveni) | ||||||
|  | 		rocnik = nastaveni.aktualni_rocnik | ||||||
|  | 		temata = s.Tema.objects.filter(rocnik=rocnik, stav=s.Problem.STAV_ZADANY) | ||||||
|  | 		ulohy = s.Uloha.objects.filter(cislo_deadline__rocnik = rocnik) | ||||||
|  | 		ulohy.union(temata) | ||||||
|  | 		qs = ulohy | ||||||
|  | 		if self.q: | ||||||
|  | 			qs = qs.filter( | ||||||
|  | 					Q(nazev__startswith=self.q)) | ||||||
|  | 		return qs | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| # Ceka na autocomplete v3 | # Ceka na autocomplete v3 | ||||||
| # class OrganizatorAutocomplete(autocomplete.Select2QuerySetView): | # class OrganizatorAutocomplete(autocomplete.Select2QuerySetView): | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue