Merge branch 'data_migrations' of gimli.ms.mff.cuni.cz:/akce/mam/git/mamweb into data_migrations
This commit is contained in:
		
						commit
						d9558a750a
					
				
					 21 changed files with 1003 additions and 154 deletions
				
			
		
							
								
								
									
										
											BIN
										
									
								
								Schema_new.dia
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Schema_new.dia
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										332
									
								
								flat.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										332
									
								
								flat.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -2,6 +2,7 @@ from django import forms | |||
| from dal import autocomplete | ||||
| from django.core.exceptions import ObjectDoesNotExist | ||||
| from django.contrib.auth.models import User | ||||
| from django.forms.models import inlineformset_factory | ||||
| 
 | ||||
| from .models import Skola, Resitel, Osoba, Problem | ||||
| import seminar.models as m | ||||
|  | @ -252,6 +253,25 @@ class VlozReseniForm(forms.Form): | |||
| 		super().__init__(*args, **kwargs) | ||||
| 		#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'), | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										32
									
								
								seminar/migrations/0077_auto_20200318_2146.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								seminar/migrations/0077_auto_20200318_2146.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | |||
| # Generated by Django 2.2.9 on 2020-03-18 20:46 | ||||
| 
 | ||||
| from django.db import migrations, models | ||||
| import django.db.models.deletion | ||||
| 
 | ||||
| 
 | ||||
| class Migration(migrations.Migration): | ||||
| 
 | ||||
|     dependencies = [ | ||||
|         ('seminar', '0076_auto_20200228_2013'), | ||||
|     ] | ||||
| 
 | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name='CastNode', | ||||
|             fields=[ | ||||
|                 ('treenode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.TreeNode')), | ||||
|                 ('nadpis', models.CharField(help_text='Nadpis podvěšené části obsahu', max_length=100, verbose_name='Nadpis')), | ||||
|             ], | ||||
|             options={ | ||||
|                 'verbose_name': 'Část (Node)', | ||||
|                 'verbose_name_plural': 'Části (Node)', | ||||
|                 'db_table': 'seminar_nodes_cast', | ||||
|             }, | ||||
|             bases=('seminar.treenode',), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='treenode', | ||||
|             name='first_child', | ||||
|             field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='father_of_first', to='seminar.TreeNode', verbose_name='první potomek'), | ||||
|         ), | ||||
|     ] | ||||
|  | @ -920,10 +920,10 @@ class Hodnoceni(SeminarModelBase): | |||
| 
 | ||||
| 
 | ||||
| 	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',  | ||||
| 		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) | ||||
| 
 | ||||
|  | @ -935,17 +935,16 @@ class Hodnoceni(SeminarModelBase): | |||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| ## FIXME: Budeme řešit později, pokud to bude potřeba. | ||||
| #def aux_generate_filename(self, filename): | ||||
| #	"""Pomocná funkce generující ošetřený název souboru v adresáři s datem""" | ||||
| #	clean = get_valid_filename( | ||||
| #		unidecode(filename.replace('/', '-').replace('\0', '')) | ||||
| #	) | ||||
| #	datedir = timezone.now().strftime('%Y-%m') | ||||
| #	fname = "%s_%s" % ( | ||||
| #		timezone.now().strftime('%Y-%m-%d-%H:%M'), | ||||
| #		clean) | ||||
| #	return os.path.join(datedir, fname) | ||||
| def aux_generate_filename(self, filename): | ||||
| 	"""Pomocná funkce generující ošetřený název souboru v adresáři s datem""" | ||||
| 	clean = get_valid_filename( | ||||
| 		unidecode(filename.replace('/', '-').replace('\0', '')) | ||||
| 	) | ||||
| 	datedir = timezone.now().strftime('%Y-%m') | ||||
| 	fname = "{}_{}".format( | ||||
| 		timezone.now().strftime('%Y-%m-%d-%H:%M'), | ||||
| 		clean) | ||||
| 	return os.path.join(datedir, fname) | ||||
| 
 | ||||
| # Django neumí jednoduše serializovat partial nebo třídu s __call__ | ||||
| # (https://docs.djangoproject.com/en/1.8/topics/migrations/), | ||||
|  | @ -988,9 +987,12 @@ class PrilohaReseni(SeminarModelBase): | |||
| 
 | ||||
| 	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') | ||||
| 	 | ||||
| 	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): | ||||
| 		return self.soubor | ||||
| 		return str(self.soubor) | ||||
| 
 | ||||
| 
 | ||||
| class Pohadka(SeminarModelBase): | ||||
|  | @ -1228,6 +1230,8 @@ class Obrazek(SeminarModelBase): | |||
| 			help_text = 'Černobílá verze obrázku do čísla',  | ||||
| 			upload_to = 'obrazky/%Y/%m/%d/', blank=True, null=True) | ||||
| 
 | ||||
| 	# TODO placement hint - chci ho tady / pred textem / za textem | ||||
| 
 | ||||
| class TreeNode(PolymorphicModel): | ||||
| 	class Meta: | ||||
| 		db_table = "seminar_nodes_treenode" | ||||
|  | @ -1242,6 +1246,7 @@ class TreeNode(PolymorphicModel): | |||
| 		on_delete = models.SET_NULL, # Vrcholy s null kořenem jsou sirotci bez ročníku  | ||||
| 		verbose_name="kořen stromu") | ||||
| 	first_child = models.ForeignKey('TreeNode', | ||||
| 		related_name='father_of_first', | ||||
| 		null = True, | ||||
| 		blank = True, | ||||
| 		on_delete=models.SET_NULL, | ||||
|  | @ -1262,15 +1267,6 @@ class TreeNode(PolymorphicModel): | |||
| 	srolovatelne = models.BooleanField(null = True, blank = True, | ||||
| 		verbose_name = "Srolovatelné", | ||||
| 		help_text = "Bude na stránce témátka možnost tuto položku skrýt") | ||||
| 	 | ||||
| 	# Slouží k debugování pro rychlé získání představy o podobě podstromu pod tímto TreeNode. | ||||
| 	def print_tree(self,indent=0): | ||||
| 		# FIXME: Tady se spoléháme na to, že nedeklarovaný primární klíč se jmenuje by default 'id', což není úplně správně | ||||
| 		print("{}{} (id: {})".format(" "*indent,self, self.id)) | ||||
| 		if self.first_child: | ||||
| 			self.first_child.print_tree(indent=indent+2) | ||||
| 		if self.succ: | ||||
| 			self.succ.print_tree(indent=indent) | ||||
| 
 | ||||
| 	def getOdkazStr(self): # String na rozcestník | ||||
| 		return self.first_child.getOdkazStr() | ||||
|  | @ -1339,6 +1335,7 @@ class MezicisloNode(TreeNode): | |||
| 		verbose_name = 'Mezičíslo (Node)' | ||||
| 		verbose_name_plural = 'Mezičísla (Node)' | ||||
| 
 | ||||
| 	# TODO: Využít TreeLib | ||||
| 	def aktualizuj_nazev(self): | ||||
| 		if self.prev: | ||||
| 			if (self.prev.get_real_instance_class() != CisloNode and | ||||
|  | @ -1469,6 +1466,14 @@ class TextNode(TreeNode): | |||
| 	def getOdkazStr(self): | ||||
| 		return str(self.text) | ||||
| 
 | ||||
| class CastNode(TreeNode): | ||||
| 	class Meta: | ||||
| 		db_table = 'seminar_nodes_cast' | ||||
| 		verbose_name = 'Část (Node)' | ||||
| 		verbose_name_plural = 'Části (Node)' | ||||
| 	 | ||||
| 	nadpis = models.CharField('Nadpis', max_length=100, help_text = 'Nadpis podvěšené části obsahu') | ||||
| 
 | ||||
| 
 | ||||
| ## FIXME: Logiku přesunout do views. | ||||
| #class VysledkyBase(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 %} | ||||
|  | @ -9,7 +9,7 @@ from django.db import transaction | |||
| import unidecode | ||||
| import logging | ||||
| 
 | ||||
| from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni, Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Osoba, Organizator, Prijemce, Tema, Uloha, Konfera, KonferaNode, TextNode, UlohaVzorakNode, RocnikNode, CisloNode, TemaVCisleNode, Text, Hodnoceni, UlohaZadaniNode, Novinky | ||||
| from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni, Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Osoba, Organizator, Prijemce, Tema, Uloha, Konfera, KonferaNode, TextNode, UlohaVzorakNode, RocnikNode, CisloNode, TemaVCisleNode, Text, Hodnoceni, UlohaZadaniNode, Novinky, TreeNode | ||||
| 
 | ||||
| from django.contrib.flatpages.models import FlatPage | ||||
| from django.contrib.sites.models import Site | ||||
|  | @ -390,17 +390,15 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): | |||
| 	co = ["téma", "záření", "stavení", "jiskření", "jelito", | ||||
| 					"drama", "kuře", "moře", "klání", "proudění", "čekání"] | ||||
| 	poc_oboru = rnd.randint(1, 2) | ||||
| 	poc_op = rnd.randint(1, 3)		 | ||||
| 
 | ||||
| 	rocnik_temata = [] | ||||
| 	k = 0 | ||||
| 	for rocnik in rocniky: | ||||
| 		k+=1 | ||||
| 		n = 0 | ||||
| 		temata = [] | ||||
| 		cisla = rocnik_cisla[k-1] | ||||
| 		for ci in range(1, 3): | ||||
| 			n+=1 | ||||
| 	# Věříme, že rocnik_cisla je pole polí čísel podle ročníků, tak si necháme dát vždycky jeden ročník a k němu příslušná čísla. | ||||
| 	for rocnik, cisla in zip(rocniky, rocnik_cisla): | ||||
| 		kod = 1 | ||||
| 		letosni_temata = [] | ||||
| 		# Do každého ročníku vymyslíme tři (zatím) témata, v každém z prvních čísel jedno | ||||
| 		for zacatek_tematu in range(1, 3): | ||||
| 			# Vygenerujeme téma | ||||
| 			t = Tema.objects.create( | ||||
| 				# atributy třídy Problem | ||||
| 				nazev=" ".join([rnd.choice(jake), rnd.choice(co)]), | ||||
|  | @ -408,22 +406,32 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): | |||
| 				zamereni=rnd.sample(["M", "F", "I", "O", "B"], poc_oboru), | ||||
| 				autor=rnd.choice(organizatori),  | ||||
| 				garant=rnd.choice(organizatori),  | ||||
| 				kod=str(n), | ||||
| 				kod=str(kod), | ||||
| 				# atributy třídy Téma | ||||
| 				tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0], | ||||
| 				rocnik=rocnik, | ||||
| 				abstrakt = "Abstrakt tematka {}".format(n) | ||||
| 				abstrakt = "Abstrakt tematka {}".format(kod) | ||||
| 			) | ||||
| 			konec_tematu = min(rnd.randint(ci, 7), len(cisla)) | ||||
| 			for i in range(ci, konec_tematu+1): | ||||
| 			kod += 1 | ||||
| 
 | ||||
| 			# Vymyslíme, kdy skončí | ||||
| 			konec_tematu = min(rnd.randint(zacatek_tematu, 7), len(cisla)) | ||||
| 
 | ||||
| 			# Vyrobíme TemaVCisleNody pro obsah | ||||
| 			for i in range(zacatek_tematu, konec_tematu+1): | ||||
| 				node = TemaVCisleNode.objects.create(tema = t) | ||||
| 				# FIXME: Není to off-by-one? | ||||
| 				otec = cisla[i-1].cislonode | ||||
| 				otec_syn(otec, node) | ||||
| 
 | ||||
| 			t.opravovatele.set(rnd.sample(organizatori, poc_op)) | ||||
| 			# Vymyslíme, kdo to bude opravovat | ||||
| 			poc_opravovatelu = rnd.randint(1, 3) | ||||
| 			t.opravovatele.set(rnd.sample(organizatori, poc_opravovatelu)) | ||||
| 
 | ||||
| 			# Uložíme všechno | ||||
| 			t.save() | ||||
| 			temata.append((ci, konec_tematu, t)) | ||||
| 		rocnik_temata.append(temata) | ||||
| 			letosni_temata.append((zacatek_tematu, konec_tematu, t)) | ||||
| 		rocnik_temata.append(letosni_temata) | ||||
| 	return rocnik_temata | ||||
| 
 | ||||
| 
 | ||||
|  | @ -448,84 +456,103 @@ def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori) | |||
| 		"netriviální aplikace diferenciálních rovnic", "zadání je vnitřně" | ||||
| 		"sporné", "nepopsatelně jednoduché", "pokud jste na to nepřišli," | ||||
| 		"tak jste fakt hloupí"] | ||||
| 	k = 0 | ||||
| 	for rocnik in rocniky: | ||||
| 		k+=1 | ||||
| 		cisla = rocnik_cisla[k-1] | ||||
| 		temata = rocnik_temata[k-1] | ||||
| 		for ci in range(len(cisla)): | ||||
| 			print("Generuji {}-té číslo".format(ci)) | ||||
| 			cislo = cisla[ci-1]	 | ||||
| 			mozna_tema_vcn = cislo.cislonode.first_child | ||||
| 			# kdybyste nad tím někdo taky přemýšleli, tak vcn == VCisleNode :) | ||||
| 			while mozna_tema_vcn != None: | ||||
| 				if type(mozna_tema_vcn) != TemaVCisleNode: | ||||
| 					mozna_tema_vcn = mozna_tema_vcn.succ | ||||
| 	# Ke každému ročníku si vezmeme příslušná čísla a témata | ||||
| 	for rocnik, cisla, temata in zip(rocniky, rocnik_cisla, rocnik_temata): | ||||
| 		# Do každého čísla nagenerujeme ke každému témátku pár úložek | ||||
| 		for cislo in cisla: | ||||
| 			print("Generuji úložky do {}-tého čísla".format(cislo.poradi)) | ||||
| 			# Vzorák bude o dvě čísla dál | ||||
| 			cislo_se_vzorakem = Cislo.objects.filter( | ||||
| 					rocnik=rocnik, | ||||
| 					poradi=str(int(cislo.poradi) + 2), | ||||
| 					) | ||||
| 			# Pokud není číslo pro vzorák, tak se dá do posledního čísla (i kdyby tam mělo být zadání i řešení...) | ||||
| 			# Tohle sice umožňuje vygenerovat vzorák do čísla dávno po konci témátka, ale to nám pro jednoduchost nevadí. | ||||
| 			if len(cislo_se_vzorakem) == 0: | ||||
| 				cislo_se_vzorakem = cisla[-1] | ||||
| 			else: | ||||
| 				cislo_se_vzorakem = cislo_se_vzorakem.first() | ||||
| 
 | ||||
| 			# FIXME: Tenhle generátor dát asi někam jinam | ||||
| 			def potomci(node): | ||||
| 				if not isinstance(node, TreeNode): | ||||
| 					raise ValueError("Typ {} nemá potomky", type(node)) | ||||
| 				current_child = node.first_child | ||||
| 				while current_child is not None: | ||||
| 					yield current_child | ||||
| 					current_child = current_child.succ | ||||
| 
 | ||||
| 			for mozna_tema_node in potomci(cislo.cislonode): | ||||
| 				if not isinstance(mozna_tema_node, TemaVCisleNode): | ||||
| 					continue | ||||
| 				else: | ||||
| 					tema = mozna_tema_vcn.tema | ||||
| 				tema_node = mozna_tema_node | ||||
| 				tema = tema_node.tema | ||||
| 					 | ||||
| 				if not temata[int(tema.kod)-1][1] >= ci+2: | ||||
| 					mozna_tema_vcn = mozna_tema_vcn.succ | ||||
| 				# Pokud už témátko skončilo, žádné úložky negenerujeme | ||||
| 				# FIXME: Bylo by hezčí, kdyby se čísla předávala jako Cislo a ne jako int v té trojici (start, konec, tema) | ||||
| 				if not temata[int(tema.kod)-1][1] >= int(cislo_se_vzorakem.poradi): | ||||
| 					continue | ||||
| 					 | ||||
| 				for i in range(1, rnd.randint(1, 4)): | ||||
| 					poc_op = rnd.randint(1, 4) | ||||
| 					poc_oboru = rnd.randint(1, 2) | ||||
| 					p = Uloha.objects.create( | ||||
| 				# Generujeme 1 až 4 úložky k tomuto témátku do tohoto čísla | ||||
| 				for kod in range(1, rnd.randint(1, 4)): | ||||
| 					u = Uloha.objects.create( | ||||
| 						nazev=": ".join([tema.nazev,  | ||||
| 							"úloha {}.".format(i)]), | ||||
| 							"úloha {}.".format(kod)]), | ||||
| 						nadproblem=tema, | ||||
| 						stav=Problem.STAV_ZADANY, | ||||
| 						zamereni=tema.zamereni, | ||||
| 						autor=tema.autor,  | ||||
| 						garant=tema.garant,  | ||||
| 						kod=str(i),  | ||||
| 						kod=str(kod),  | ||||
| 						cislo_zadani=cislo,  | ||||
| 						cislo_reseni=cisla[ci+2-1],  | ||||
| 						cislo_deadline=cisla[ci+2-1],  | ||||
| 						cislo_reseni=cislo_se_vzorakem,  | ||||
| 						cislo_deadline=cislo_se_vzorakem,  | ||||
| 						max_body = rnd.randint(1, 8) | ||||
| 					) | ||||
| 					 | ||||
| 					p.opravovatele.set(rnd.sample(organizatori, poc_op))   | ||||
| 					poc_opravovatelu = rnd.randint(1, 4) | ||||
| 					u.opravovatele.set(rnd.sample(organizatori, poc_opravovatelu))   | ||||
| 					 | ||||
| 					text_zadani = Text.objects.create( | ||||
| 						na_web = " ".join( | ||||
| 						[rnd.choice(sloveso),  | ||||
| 						rnd.choice(koho),  | ||||
| 						rnd.choice(ceho),  | ||||
| 						rnd.choice(jmeno),  | ||||
| 						rnd.choice(kde)] | ||||
| 						), | ||||
| 						do_cisla = " ".join( | ||||
| 					# Samotný obsah následně vzniklého Textu zadání | ||||
| 					obsah = " ".join( | ||||
| 						[rnd.choice(sloveso),  | ||||
| 						rnd.choice(koho),  | ||||
| 						rnd.choice(ceho),  | ||||
| 						rnd.choice(jmeno),  | ||||
| 						rnd.choice(kde)] | ||||
| 						) | ||||
| 					) | ||||
| 					text_zadani = Text.objects.create( | ||||
| 						na_web = obsah, | ||||
| 						do_cisla = obsah, | ||||
| 						) | ||||
| 					zad = TextNode.objects.create(text = text_zadani) | ||||
| 					uloha_zadani = UlohaZadaniNode.objects.create(uloha=p, first_child = zad) | ||||
| 					p.ulohazadaninode = uloha_zadani | ||||
| 					otec_syn(mozna_tema_vcn, uloha_zadani)  | ||||
| 					# TODO dělá se podproblém takto??? TODO | ||||
| 					uloha_zadani = UlohaZadaniNode.objects.create(uloha=u, first_child = zad) | ||||
| 					u.ulohazadaninode = uloha_zadani | ||||
| 
 | ||||
| 					# FIXME: Tohle dává zadání vždy jako prvního potomka témátka, spec. se naskládají v opačném pořadí a nemůže mezi nimi vzniknout žádný (orgo-)text | ||||
| 					otec_syn(tema_node, uloha_zadani) | ||||
| 					 | ||||
| 					# Text vzoráku stejně | ||||
| 					obsah = rnd.choice(reseni) | ||||
| 					text_vzoraku = Text.objects.create( | ||||
| 						na_web = rnd.choice(reseni), | ||||
| 						do_cisla = rnd.choice(reseni) | ||||
| 						na_web = obsah, | ||||
| 						do_cisla = obsah, | ||||
| 					) | ||||
| 					vzorak = TextNode.objects.create(text = text_vzoraku) | ||||
| 					uloha_vzorak = UlohaVzorakNode.objects.create(uloha=p, first_child = vzorak) | ||||
| 					p.UlohaVzorakNode = uloha_vzorak | ||||
| 					res_tema_vcn = cisla[ci+2-1].cislonode.first_child | ||||
| 					while res_tema_vcn.tema != tema: | ||||
| 						res_tema_vcn = res_tema_vcn.succ | ||||
| 					otec_syn(res_tema_vcn, uloha_vzorak) | ||||
| 					uloha_vzorak = UlohaVzorakNode.objects.create(uloha=u, first_child = vzorak) | ||||
| 					u.UlohaVzorakNode = uloha_vzorak | ||||
| 
 | ||||
| 					# Najdeme správný TemaVCisleNode pro vložení vzoráku | ||||
| 					res_tema_node = None; | ||||
| 					for node in potomci(cislo_se_vzorakem.cislonode): | ||||
| 						if isinstance(node, TemaVCisleNode) and node.tema == tema: | ||||
| 							res_tema_node = node | ||||
| 					if res_tema_node is None: | ||||
| 						raise LookupError("Nenalezen Node pro vložení vzoráku") | ||||
| 					# FIXME: Stejný problém jako výše: vzoráky se dají na začátek v opačném pořadí. | ||||
| 					otec_syn(res_tema_node, uloha_vzorak) | ||||
| 					 | ||||
| 					p.save() | ||||
| 			 | ||||
| 				mozna_tema_vcn = mozna_tema_vcn.succ | ||||
| 					u.save() | ||||
| 	return | ||||
| 
 | ||||
| def gen_novinky(rnd, organizatori): | ||||
|  | @ -604,6 +631,7 @@ def create_test_data(size = 6, rnd = None): | |||
| 	rocniky = gen_rocniky(last_rocnik, size) | ||||
| 	 | ||||
| 	# cisla | ||||
| 	# rocnik_cisla je pole polí čísel (typ Cislo), vnitřní pole odpovídají jednotlivým ročníkům. | ||||
| 	rocnik_cisla = gen_cisla(rnd, rocniky)	 | ||||
| 	 | ||||
| 	# generování obyčejných úloh do čísel | ||||
|  | @ -611,6 +639,7 @@ def create_test_data(size = 6, rnd = None): | |||
| 	 | ||||
| 	# generování témat, zatím v prvních třech číslech po jednom | ||||
| 	# FIXME: více témat | ||||
| 	# rocnik_temata je pole polí trojic (první číslo :int, poslední číslo :int, téma:Tema), přičemž každé vnitřní pole odpovídá ročníku a FIXME: je to takhle fuj a když to někdo vidí poprvé, tak je z toho smutný, protože vůbec neví, co se děje a co má čekat. | ||||
| 	rocnik_temata = gen_temata(rnd, rocniky, rocnik_cisla, organizatori) | ||||
| 	 | ||||
| 	# generování úloh k tématům ve všech číslech | ||||
|  |  | |||
							
								
								
									
										124
									
								
								seminar/treelib.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								seminar/treelib.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,124 @@ | |||
| from django.core.exceptions import ObjectDoesNotExist | ||||
| # NOTE: node.prev a node.succ jsou implementovány přímo v models.TreeNode | ||||
| # TODO: Všechny tyto funkce se naivně spoléhají na to, že jako parametr dostanou nějaký TreeNode (některé možná zvládnou i None) | ||||
| 
 | ||||
| # Slouží k debugování pro rychlé získání představy o podobě podstromu pod tímto TreeNode. | ||||
| def print_tree(node,indent=0): | ||||
| 	# FIXME: Tady se spoléháme na to, že nedeklarovaný primární klíč se jmenuje by default 'id', což není úplně správně | ||||
| 	print("{}{} (id: {})".format(" "*indent,node, node.id)) | ||||
| 	if node.first_child: | ||||
| 		print_tree(node.first_child, indent=indent+2) | ||||
| 	if node.succ: | ||||
| 		print_tree(node.succ, indent=indent) | ||||
| 
 | ||||
| # Django je trošku hloupé, takže node.prev nevrací None, ale hází django.core.exceptions.ObjectDoesNotExist | ||||
| def safe_pred(node): | ||||
| 	try: | ||||
| 		return node.prev | ||||
| 	except ObjectDoesNotExist: | ||||
| 		return None | ||||
| 
 | ||||
| # A to samé pro .father_of_first | ||||
| def safe_father_of_first(node): | ||||
| 		return node.prev | ||||
| 	except ObjectDoesNotExist: | ||||
| 		return None | ||||
| 
 | ||||
| ## Rodinné vztahy | ||||
| def get_parent(node): | ||||
| 	# Nejdřív získáme prvního potomka... | ||||
| 	while safe_pred(node) is not None: | ||||
| 		node = safe_pred(node) | ||||
| 	# ... a z prvního potomka umíme najít rodiče | ||||
| 	return safe_father_of_first(node) | ||||
| 
 | ||||
| # Obecný next: další Node v "the-right-order" pořadí (já, pak potomci, pak sousedé) | ||||
| def general_next(node): | ||||
| 	# Máme potomka? | ||||
| 	if node.first_child is not None: | ||||
| 		return node.first_child | ||||
| 	# Nemáme potomka. | ||||
| 	# Chceme najít následníka sebe, nebo některého (toho nejblíž příbuzného) z našich předků (tatínka, dědečka, ...) | ||||
| 	while node.succ is None: | ||||
| 		node = get_parent(node) | ||||
| 		if node is None: | ||||
| 			return None	# žádný z předků nemá následníka, takže žádny vrchol nenásleduje. | ||||
| 	return node.succ | ||||
| 
 | ||||
| def last_brother(node): | ||||
| 	while node.succ is not None: | ||||
| 		node = node.succ | ||||
| 	return node | ||||
| 
 | ||||
| def general_prev(node): | ||||
| 	# Předchůdce je buď rekurzivně poslední potomek předchůdce, nebo náš otec. | ||||
| 	# Otce vyřešíme nejdřív: | ||||
| 	if safe_pred(node) is None: | ||||
| 		return safe_father_of_first(node) | ||||
| 	pred = safe_pred(node) | ||||
| 	while pred.first_child is not None: | ||||
| 		pred = last_brother(pred.first_child) | ||||
| 	# pred nyní nemá žádné potomky, takže je to poslední rekurzivní potomek původního předchůdce | ||||
| 	return pred | ||||
| 
 | ||||
| # Generátor bratrů | ||||
| # Generátor potomků níže spoléhá na to, že se tohle dá volat i s parametrem None. | ||||
| def all_brothers(node): | ||||
| 	current = node | ||||
| 	while current is not None: | ||||
| 		yield current | ||||
| 		current = current.succ | ||||
| 
 | ||||
| # Generátor potomků | ||||
| def all_children(node): | ||||
| 	brothers = all_brothers(node.first_child) | ||||
| 	for br in brothers: | ||||
| 		yield br | ||||
| 
 | ||||
| # Generátor následníků v "the-right-order" | ||||
| 
 | ||||
| ## Filtrační hledání | ||||
| # Najdi dalšího bratra nějakého typu, nebo None. | ||||
| # hledá i podtřídy, i.e. get_next_brother_of_type(neco, TreeNode) je prostě succ. | ||||
| def get_next_brother_of_type(current, type): | ||||
| 	pass | ||||
| def get_prev_brother_of_type(current, type): | ||||
| 	pass | ||||
| 
 | ||||
| # Totéž pro "the-right-order" pořadí | ||||
| def get_next_node_of_type(current, type): | ||||
| 	pass | ||||
| def get_next_node_of_type(current, type): | ||||
| 	pass | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| # Editace stromu: | ||||
| def create_node_after(predecessor, type, **kwargs): | ||||
| 	pass | ||||
| 
 | ||||
| # Vyrábí prvního syna, ostatní nalepí za (existují-li) | ||||
| def create_child(parent, type, **kwargs): | ||||
| 	pass | ||||
| 
 | ||||
| def create_node_before(some, arguments, but, i, dont, know, which, yet): | ||||
| 	pass | ||||
| 	# Tohle bude hell. | ||||
| 
 | ||||
| # ValueError, pokud je (aspoň) jeden parametr None | ||||
| def swap(node, other): | ||||
| 	pass | ||||
| 
 | ||||
| def swap_pred(node): | ||||
| 	pass | ||||
| def swap_succ(node): | ||||
| 	pass | ||||
| 
 | ||||
| # Rotace stromu | ||||
| # Dokumentace viz wiki: | ||||
| # (lower bude jednoduchá rotace, ne mega, existence jednoduché rotace mi došla až po nakreslení obrázku) | ||||
| def raise_node(node): | ||||
| 	pass | ||||
| def lower_node(node): | ||||
| 	pass | ||||
|  | @ -109,6 +109,7 @@ urlpatterns = [ | |||
| 	path('auth/resitel/', views.ResitelView.as_view(), name='seminar_resitel'), | ||||
| 	path('autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), | ||||
| 	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/change_password/', views.PasswordChangeView.as_view(), name='change_password'), | ||||
| 	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/submit_solution', views.SubmitSolutionView.as_view(),name='seminar_nahraj_reseni'), | ||||
| 
 | ||||
| 	path('', views.TitulniStranaView.as_view(), name='titulni_strana'), | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,6 +4,8 @@ import datetime | |||
| from django.contrib.auth.decorators import user_passes_test | ||||
| from html.parser import HTMLParser  | ||||
| 
 | ||||
| import seminar.models as m | ||||
| 
 | ||||
| staff_member_required = user_passes_test(lambda u: u.is_staff) | ||||
| 
 | ||||
| class FirstTagParser(HTMLParser): | ||||
|  | @ -43,7 +45,6 @@ def from_roman(rom): | |||
| 
 | ||||
| 
 | ||||
| def seznam_problemu(): | ||||
| 	from .models import Problem, Resitel, Rocnik, Reseni, Cislo | ||||
| 	problemy = [] | ||||
| 
 | ||||
| 	# Pomocna fce k formatovani problemovych hlasek | ||||
|  | @ -65,27 +66,26 @@ def seznam_problemu(): | |||
| 
 | ||||
| 	# Duplicita jmen | ||||
| 	jmena = {} | ||||
| 	for r in Resitel.objects.all(): | ||||
| 	for r in m.Resitel.objects.all(): | ||||
| 		j = r.plne_jmeno() | ||||
| 		if j not in jmena: | ||||
| 			jmena[j] = [] | ||||
| 		jmena[j].append(r) | ||||
| 	for j in jmena: | ||||
| 		if len(jmena[j]) > 1: | ||||
| 			prb(Resitel, u'Duplicitní jméno "%s"' % (j, ), jmena[j]) | ||||
| 			prb(m.Resitel, u'Duplicitní jméno "%s"' % (j, ), jmena[j]) | ||||
| 
 | ||||
| 	# Data maturity a narození | ||||
| 	for r in Resitel.objects.all(): | ||||
| 	for r in m.Resitel.objects.all(): | ||||
| 		if not r.rok_maturity: | ||||
| 			prb(Resitel, u'Neznámý rok maturity', [r]) | ||||
| 			prb(m.Resitel, u'Neznámý rok maturity', [r]) | ||||
| 		if r.rok_maturity and (r.rok_maturity < 1990 or r.rok_maturity > datetime.date.today().year + 10): | ||||
| 			prb(Resitel, u'Podezřelé datum maturity', [r]) | ||||
| 			prb(m.Resitel, u'Podezřelé datum maturity', [r]) | ||||
| 		if r.datum_narozeni and (r.datum_narozeni.year < 1970 or r.datum_narozeni.year > datetime.date.today().year - 12): | ||||
| 			prb(Resitel, u'Podezřelé datum narození', [r]) | ||||
| 			prb(m.Resitel, u'Podezřelé datum narození', [r]) | ||||
| #        if not r.email: | ||||
| #            prb(Resitel, u'Neznámý email', [r]) | ||||
| 
 | ||||
| 	return problemy | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										2
									
								
								seminar/views/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								seminar/views/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| from .views_all import * | ||||
| from .autocomplete import * | ||||
							
								
								
									
										63
									
								
								seminar/views/autocomplete.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								seminar/views/autocomplete.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,63 @@ | |||
| from dal import autocomplete | ||||
| from django.shortcuts import get_object_or_404 | ||||
| 
 | ||||
| import seminar.models as m | ||||
| from .helpers import LoginRequiredAjaxMixin | ||||
| 
 | ||||
| class SkolaAutocomplete(autocomplete.Select2QuerySetView): | ||||
| 	def get_queryset(self): | ||||
|         	# Don't forget to filter out results depending on the visitor ! | ||||
| 		qs = m.Skola.objects.all() | ||||
| 		if self.q: | ||||
| 			qs = qs.filter( | ||||
| 				Q(nazev__istartswith=self.q)| | ||||
| 				Q(kratky_nazev__istartswith=self.q)| | ||||
| 				Q(ulice__istartswith=self.q)| | ||||
| 				Q(mesto__istartswith=self.q)) | ||||
| 		 | ||||
| 		return qs | ||||
| 
 | ||||
| class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetView): | ||||
| 	def get_queryset(self): | ||||
| 		qs = m.Resitel.objects.all() | ||||
| 		if self.q: | ||||
| 			qs = qs.filter( | ||||
| 				Q(osoba__jmeno__startswith=self.q)| | ||||
| 				Q(osoba__prijmeni__startswith=self.q)| | ||||
| 				Q(osoba__prezdivka__startswith=self.q) | ||||
| 				) | ||||
| 		return qs | ||||
| 
 | ||||
| class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView): | ||||
| 	def get_queryset(self): | ||||
| 		nastaveni = get_object_or_404(m.Nastaveni) | ||||
| 		rocnik = nastaveni.aktualni_rocnik | ||||
| 		temata = m.Tema.objects.filter(rocnik=rocnik, stav=m.Problem.STAV_ZADANY) | ||||
| 		ulohy = m.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 | ||||
| # class OrganizatorAutocomplete(autocomplete.Select2QuerySetView): | ||||
| # 	def get_queryset(self): | ||||
| # 		if not self.request.user.is_authenticated(): | ||||
| # 			return Organizator.objects.none() | ||||
| # | ||||
| # 		qs = aktivniOrganizatori() | ||||
| # | ||||
| # 		if self.q: | ||||
| # 			if self.q[0] == "!": | ||||
| # 				qs = Organizator.objects.all() | ||||
| # 				query = self.q[1:] | ||||
| # 			else: | ||||
| # 				query = self.q | ||||
| # 			qs = qs.filter( | ||||
| # 				Q(prezdivka__isstartswith=query)| | ||||
| # 				Q(user__first_name__isstartswith=query)| | ||||
| # 				Q(user__last_name__isstartswith=query)) | ||||
| # | ||||
| # 		return qs | ||||
							
								
								
									
										8
									
								
								seminar/views/helpers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								seminar/views/helpers.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| from dal import autocomplete | ||||
| 
 | ||||
| class LoginRequiredAjaxMixin(object): | ||||
| 	def dispatch(self, request, *args, **kwargs): | ||||
| 		#if request.is_ajax() and not request.user.is_authenticated: # Pokud to otevřu jako stránku, tak se omezení neuplatní, takže to asi nechceme | ||||
| 		if not request.user.is_authenticated: | ||||
| 			return JsonResponse(data={'results': [], 'pagination': {}}, status=401) | ||||
| 		return super(LoginRequiredAjaxMixin, self).dispatch(request, *args, **kwargs) | ||||
							
								
								
									
										91
									
								
								seminar/views/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								seminar/views/utils.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,91 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| 
 | ||||
| import datetime | ||||
| from django.contrib.auth.decorators import user_passes_test | ||||
| from html.parser import HTMLParser  | ||||
| 
 | ||||
| import seminar.models as m | ||||
| 
 | ||||
| staff_member_required = user_passes_test(lambda u: u.is_staff) | ||||
| 
 | ||||
| class FirstTagParser(HTMLParser): | ||||
| 	def __init__(self, *args, **kwargs): | ||||
| 		self.firstTag = None | ||||
| 		super().__init__(*args, **kwargs) | ||||
| 	def handle_data(self, data): | ||||
| 		if self.firstTag == None: | ||||
| 			self.firstTag = data | ||||
| 	 | ||||
| def histogram(seznam): | ||||
| 	d = {} | ||||
| 	for i in seznam: | ||||
| 		if i not in d: | ||||
| 			d[i] = 0 | ||||
| 		d[i] += 1 | ||||
| 	return d | ||||
| 
 | ||||
| 
 | ||||
| roman_numerals = zip((1000, 900,  500, 400, 100,  90, 50,  40, 10,  9,   5,  4,   1), | ||||
| 					 ('M',  'CM', 'D', 'CD','C', 'XC','L','XL','X','IX','V','IV','I')) | ||||
| 
 | ||||
| def roman(num): | ||||
| 	res = "" | ||||
| 	for i, n in roman_numerals: | ||||
| 		res += n * (num // i) | ||||
| 		num %= i | ||||
| 	return res | ||||
| 
 | ||||
| def from_roman(rom): | ||||
| 	if not rom: | ||||
| 		return 0 | ||||
| 	for i, n in roman_numerals: | ||||
| 		if rom.upper().startswith(n): | ||||
| 			return i + from_roman(rom[len(n):]) | ||||
| 	raise Exception('Invalid roman numeral: "%s"', rom) | ||||
| 
 | ||||
| 
 | ||||
| def seznam_problemu(): | ||||
| 	problemy = [] | ||||
| 
 | ||||
| 	# Pomocna fce k formatovani problemovych hlasek | ||||
| 	def prb(cls, msg, objs=None): | ||||
| 		s = u'<b>%s:</b> %s' % (cls.__name__, msg) | ||||
| 		if objs: | ||||
| 			s += u' [' | ||||
| 			for o in objs: | ||||
| 				try: | ||||
| 					url = o.admin_url() | ||||
| 				except: | ||||
| 					url = None | ||||
| 				if url: | ||||
| 					s += u'<a href="%s">%s</a>, ' % (url, o.pk, ) | ||||
| 				else: | ||||
| 					s += u'%s, ' % (o.pk, ) | ||||
| 			s = s[:-2] + u']' | ||||
| 		problemy.append(s) | ||||
| 
 | ||||
| 	# Duplicita jmen | ||||
| 	jmena = {} | ||||
| 	for r in m.Resitel.objects.all(): | ||||
| 		j = r.plne_jmeno() | ||||
| 		if j not in jmena: | ||||
| 			jmena[j] = [] | ||||
| 		jmena[j].append(r) | ||||
| 	for j in jmena: | ||||
| 		if len(jmena[j]) > 1: | ||||
| 			prb(m.Resitel, u'Duplicitní jméno "%s"' % (j, ), jmena[j]) | ||||
| 
 | ||||
| 	# Data maturity a narození | ||||
| 	for r in m.Resitel.objects.all(): | ||||
| 		if not r.rok_maturity: | ||||
| 			prb(m.Resitel, u'Neznámý rok maturity', [r]) | ||||
| 		if r.rok_maturity and (r.rok_maturity < 1990 or r.rok_maturity > datetime.date.today().year + 10): | ||||
| 			prb(m.Resitel, u'Podezřelé datum maturity', [r]) | ||||
| 		if r.datum_narozeni and (r.datum_narozeni.year < 1970 or r.datum_narozeni.year > datetime.date.today().year - 12): | ||||
| 			prb(m.Resitel, u'Podezřelé datum narození', [r]) | ||||
| #        if not r.email: | ||||
| #            prb(Resitel, u'Neznámý email', [r]) | ||||
| 
 | ||||
| 	return problemy | ||||
| 
 | ||||
| 
 | ||||
|  | @ -9,20 +9,19 @@ from django.utils.translation import ugettext as _ | |||
| from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect | ||||
| from django.db.models import Q, Sum, Count | ||||
| 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 views as auth_views | ||||
| from django.contrib.auth.models import User | ||||
| from django.contrib.auth.mixins import LoginRequiredMixin | ||||
| from django.db import transaction | ||||
| from dal import autocomplete | ||||
| 
 | ||||
| import seminar.models as s | ||||
| from .models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci | ||||
| from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci | ||||
| #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva | ||||
| from . import utils | ||||
| from seminar import utils | ||||
| from .unicodecsv import UnicodeWriter | ||||
| from .forms import PrihlaskaForm, LoginForm, ProfileEditForm | ||||
| from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm | ||||
| import seminar.forms as f | ||||
| 
 | ||||
| from datetime import timedelta, date, datetime | ||||
|  | @ -1223,6 +1222,42 @@ class AddSolutionView(LoginRequiredMixin, FormView): | |||
| 	form_class = f.VlozReseniForm | ||||
| 	success_url = '/' | ||||
| 
 | ||||
| class SubmitSolutionView(LoginRequiredMixin, CreateView): | ||||
| 	model = s.Reseni | ||||
| 	template_name = 'seminar/nahraj_reseni.html' | ||||
| 	form_class = f.NahrajReseniForm | ||||
| 	success_url = '/' | ||||
| 
 | ||||
| 	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'] | ||||
| 		if not prilohy.is_valid(): | ||||
| 			return super().form_invalid(form) | ||||
| 		with transaction.atomic(): | ||||
| 			self.object = form.save() | ||||
| 			self.object.resitele.add(Resitel.objects.get(osoba__user = self.request.user)) | ||||
| 			self.object.cas_doruceni = timezone.now()  | ||||
| 			self.object.forma = s.Reseni.FORMA_UPLOAD | ||||
| 			self.object.save() | ||||
| 
 | ||||
| 			prilohy.instance = self.object | ||||
| 			prilohy.save() | ||||
| 
 | ||||
| 		return HttpResponseRedirect(self.get_success_url()) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| def resetPasswordView(request): | ||||
| 	pass | ||||
| 
 | ||||
|  | @ -1384,58 +1419,9 @@ def prihlaskaView(request): | |||
| 
 | ||||
| 	return render(request, 'seminar/prihlaska.html', {'form': form}) | ||||
| 
 | ||||
| class SkolaAutocomplete(autocomplete.Select2QuerySetView): | ||||
| 	def get_queryset(self): | ||||
|         	# Don't forget to filter out results depending on the visitor ! | ||||
| 		qs = Skola.objects.all() | ||||
| 		if self.q: | ||||
| 			qs = qs.filter( | ||||
| 				Q(nazev__istartswith=self.q)| | ||||
| 				Q(kratky_nazev__istartswith=self.q)| | ||||
| 				Q(ulice__istartswith=self.q)| | ||||
| 				Q(mesto__istartswith=self.q)) | ||||
| 		 | ||||
| 		return qs | ||||
| 
 | ||||
| class LoginRequiredAjaxMixin(object): | ||||
| 	def dispatch(self, request, *args, **kwargs): | ||||
| 		#if request.is_ajax() and not request.user.is_authenticated: # Pokud to otevřu jako stránku, tak se omezení neuplatní, takže to asi nechceme | ||||
| 		if not request.user.is_authenticated: | ||||
| 			return JsonResponse(data={'results': [], 'pagination': {}}, status=401) | ||||
| 		return super(LoginRequiredAjaxMixin, self).dispatch(request, *args, **kwargs) | ||||
| 
 | ||||
| class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetView): | ||||
| 	def get_queryset(self): | ||||
| 		qs = Resitel.objects.all() | ||||
| 		if self.q: | ||||
| 			qs = qs.filter( | ||||
| 				Q(osoba__jmeno__startswith=self.q)| | ||||
| 				Q(osoba__prijmeni__startswith=self.q)| | ||||
| 				Q(osoba__prezdivka__startswith=self.q) | ||||
| 				) | ||||
| 		return qs | ||||
| 
 | ||||
| 
 | ||||
| # Ceka na autocomplete v3 | ||||
| # class OrganizatorAutocomplete(autocomplete.Select2QuerySetView): | ||||
| # 	def get_queryset(self): | ||||
| # 		if not self.request.user.is_authenticated(): | ||||
| # 			return Organizator.objects.none() | ||||
| # | ||||
| # 		qs = aktivniOrganizatori() | ||||
| # | ||||
| # 		if self.q: | ||||
| # 			if self.q[0] == "!": | ||||
| # 				qs = Organizator.objects.all() | ||||
| # 				query = self.q[1:] | ||||
| # 			else: | ||||
| # 				query = self.q | ||||
| # 			qs = qs.filter( | ||||
| # 				Q(prezdivka__isstartswith=query)| | ||||
| # 				Q(user__first_name__isstartswith=query)| | ||||
| # 				Q(user__last_name__isstartswith=query)) | ||||
| # | ||||
| # 		return qs | ||||
| 
 | ||||
| 
 | ||||
| # FIXME: Tohle asi vlastně vůbec nepatří do aplikace 'seminar' | ||||
| class LoginView(auth_views.LoginView): | ||||
							
								
								
									
										1
									
								
								sitetree_new.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								sitetree_new.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Loading…
	
		Reference in a new issue
	
	 Anet
						Anet