Merge pull request 'Odstrel Modelu Tvorba' (!66) from odstrel_modelu_tvorba into master
Reviewed-on: #66
This commit is contained in:
		
						commit
						87a76b17ef
					
				
					 33 changed files with 1540 additions and 749 deletions
				
			
		|  | @ -216,57 +216,57 @@ | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "add_cislo", | 		"codename": "add_cislo", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "cislo" | 		"ct_model": "cislo" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "change_cislo", | 		"codename": "change_cislo", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "cislo" | 		"ct_model": "cislo" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "delete_cislo", | 		"codename": "delete_cislo", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "cislo" | 		"ct_model": "cislo" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "view_cislo", | 		"codename": "view_cislo", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "cislo" | 		"ct_model": "cislo" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "add_clanek", | 		"codename": "add_clanek", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "clanek" | 		"ct_model": "clanek" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "change_clanek", | 		"codename": "change_clanek", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "clanek" | 		"ct_model": "clanek" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "delete_clanek", | 		"codename": "delete_clanek", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "clanek" | 		"ct_model": "clanek" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "view_clanek", | 		"codename": "view_clanek", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "clanek" | 		"ct_model": "clanek" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "add_deadline", | 		"codename": "add_deadline", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "deadline" | 		"ct_model": "deadline" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "change_deadline", | 		"codename": "change_deadline", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "deadline" | 		"ct_model": "deadline" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "view_deadline", | 		"codename": "view_deadline", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "deadline" | 		"ct_model": "deadline" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
|  | @ -371,22 +371,22 @@ | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "add_pohadka", | 		"codename": "add_pohadka", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "pohadka" | 		"ct_model": "pohadka" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "change_pohadka", | 		"codename": "change_pohadka", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "pohadka" | 		"ct_model": "pohadka" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "delete_pohadka", | 		"codename": "delete_pohadka", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "pohadka" | 		"ct_model": "pohadka" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "view_pohadka", | 		"codename": "view_pohadka", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "pohadka" | 		"ct_model": "pohadka" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
|  | @ -411,22 +411,22 @@ | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "add_problem", | 		"codename": "add_problem", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "problem" | 		"ct_model": "problem" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "change_problem", | 		"codename": "change_problem", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "problem" | 		"ct_model": "problem" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "delete_problem", | 		"codename": "delete_problem", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "problem" | 		"ct_model": "problem" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "view_problem", | 		"codename": "view_problem", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "problem" | 		"ct_model": "problem" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
|  | @ -441,22 +441,22 @@ | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "add_rocnik", | 		"codename": "add_rocnik", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "rocnik" | 		"ct_model": "rocnik" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "change_rocnik", | 		"codename": "change_rocnik", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "rocnik" | 		"ct_model": "rocnik" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "delete_rocnik", | 		"codename": "delete_rocnik", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "rocnik" | 		"ct_model": "rocnik" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "view_rocnik", | 		"codename": "view_rocnik", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "rocnik" | 		"ct_model": "rocnik" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
|  | @ -541,42 +541,42 @@ | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "add_tema", | 		"codename": "add_tema", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "tema" | 		"ct_model": "tema" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "change_tema", | 		"codename": "change_tema", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "tema" | 		"ct_model": "tema" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "delete_tema", | 		"codename": "delete_tema", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "tema" | 		"ct_model": "tema" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "view_tema", | 		"codename": "view_tema", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "tema" | 		"ct_model": "tema" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "add_uloha", | 		"codename": "add_uloha", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "uloha" | 		"ct_model": "uloha" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "change_uloha", | 		"codename": "change_uloha", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "uloha" | 		"ct_model": "uloha" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "delete_uloha", | 		"codename": "delete_uloha", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "uloha" | 		"ct_model": "uloha" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"codename": "view_uloha", | 		"codename": "view_uloha", | ||||||
| 		"ct_app_label": "seminar", | 		"ct_app_label": "tvorba", | ||||||
| 		"ct_model": "uloha" | 		"ct_model": "uloha" | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								odevzdavatko/migrations/0004_tvorba_pre.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								odevzdavatko/migrations/0004_tvorba_pre.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 01:06 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('odevzdavatko', '0003_odstrel_odevzdavatka_post'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |     ] | ||||||
							
								
								
									
										35
									
								
								odevzdavatko/migrations/0005_tvorba_relink.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								odevzdavatko/migrations/0005_tvorba_relink.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 13:18 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('tvorba', '0001_tvorba_create'), | ||||||
|  |         ('odevzdavatko', '0004_tvorba_pre'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='hodnoceni', | ||||||
|  |             name='cislo_body', | ||||||
|  |             field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='tvorba.cislo', verbose_name='číslo pro body'), | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='hodnoceni', | ||||||
|  |             name='deadline_body', | ||||||
|  |             field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='tvorba.deadline', verbose_name='deadline pro body'), | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='hodnoceni', | ||||||
|  |             name='problem', | ||||||
|  |             field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='tvorba.problem', verbose_name='problém'), | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='reseni', | ||||||
|  |             name='problem', | ||||||
|  |             field=models.ManyToManyField(help_text='Problém', through='odevzdavatko.Hodnoceni', to='tvorba.problem', verbose_name='problém'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										14
									
								
								odevzdavatko/migrations/0006_tvorba_post.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								odevzdavatko/migrations/0006_tvorba_post.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 21:34 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('odevzdavatko', '0005_tvorba_relink'), | ||||||
|  |         ('tvorba', '0003_tvorba_post'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |     ] | ||||||
|  | @ -9,7 +9,7 @@ from django.urls import reverse_lazy | ||||||
| from django.utils import timezone | from django.utils import timezone | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| 
 | 
 | ||||||
| import seminar.models as am # tvorba | import tvorba.models as am | ||||||
| from seminar.models import base as bm | from seminar.models import base as bm | ||||||
| 
 | 
 | ||||||
| from odevzdavatko.utils import vzorecek_na_prepocet, inverze_vzorecku_na_prepocet | from odevzdavatko.utils import vzorecek_na_prepocet, inverze_vzorecku_na_prepocet | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								personalni/migrations/0014_tvorba_pre.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								personalni/migrations/0014_tvorba_pre.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 01:07 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('personalni', '0013_odstrel_odevzdavatka_post'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |     ] | ||||||
							
								
								
									
										14
									
								
								personalni/migrations/0015_tvorba_post.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								personalni/migrations/0015_tvorba_post.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 21:35 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('personalni', '0014_tvorba_pre'), | ||||||
|  |         ('tvorba', '0003_tvorba_post'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |     ] | ||||||
|  | @ -20,7 +20,7 @@ | ||||||
| <h2><strong>Tvorba čísla</strong></h2> | <h2><strong>Tvorba čísla</strong></h2> | ||||||
| 
 | 
 | ||||||
| <ul> | <ul> | ||||||
| 	<li><a href="{% url 'admin:seminar_problem_add' %}"><strong>přidat téma</strong></a></li> | 	<li><a href="{% url 'admin:tvorba_problem_add' %}"><strong>přidat téma</strong></a></li> | ||||||
| 	<li><strong>korektury</strong> | 	<li><strong>korektury</strong> | ||||||
| 	<ul> | 	<ul> | ||||||
| 		<li><a href="{% url 'korektury_list' %}">korekturování</a></li> | 		<li><a href="{% url 'korektury_list' %}">korekturování</a></li> | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								seminar/migrations/0136_tvorba_pre.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								seminar/migrations/0136_tvorba_pre.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 01:06 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('seminar', '0135_odstrel_odevzdavatka_post'), | ||||||
|  |         ('odevzdavatko', '0004_tvorba_pre'), | ||||||
|  |         ('various', '0004_tvorba_pre'), | ||||||
|  |         ('soustredeni', '0004_tvorba_pre'), | ||||||
|  |         ('personalni', '0014_tvorba_pre'), | ||||||
|  |         # Polymorphic: | ||||||
|  |         ('contenttypes', '0002_remove_content_type_name'), | ||||||
|  |         # Taggit | ||||||
|  |         ('taggit', '0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |     ] | ||||||
							
								
								
									
										59
									
								
								seminar/migrations/0137_tvorba_unmanage.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								seminar/migrations/0137_tvorba_unmanage.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,59 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 11:19 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('seminar', '0136_tvorba_pre'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Problemy_Opravovatele', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(primary_key=True, serialize=False)), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'db_table': 'seminar_problemy_opravovatele', | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='cislo', | ||||||
|  |             options={'managed': False, 'ordering': ['-rocnik__rocnik', '-poradi'], 'verbose_name': 'Číslo', 'verbose_name_plural': 'Čísla'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='clanek', | ||||||
|  |             options={'managed': False, 'verbose_name': 'Článek', 'verbose_name_plural': 'Články'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='deadline', | ||||||
|  |             options={'managed': False, 'ordering': ['deadline'], 'verbose_name': 'Deadline', 'verbose_name_plural': 'Deadliny'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='pohadka', | ||||||
|  |             options={'managed': False, 'ordering': ['vytvoreno'], 'verbose_name': 'Pohádka', 'verbose_name_plural': 'Pohádky'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='problem', | ||||||
|  |             options={'managed': False, 'ordering': ['nazev'], 'verbose_name': 'Problém', 'verbose_name_plural': 'Problémy'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='rocnik', | ||||||
|  |             options={'managed': False, 'ordering': ['-rocnik'], 'verbose_name': 'Ročník', 'verbose_name_plural': 'Ročníky'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='tema', | ||||||
|  |             options={'managed': False, 'verbose_name': 'Téma', 'verbose_name_plural': 'Témata'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='uloha', | ||||||
|  |             options={'managed': False, 'verbose_name': 'Úloha', 'verbose_name_plural': 'Úlohy'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='zmrazenavysledkovka', | ||||||
|  |             options={'managed': False, 'verbose_name': 'Zmražená výsledkovka', 'verbose_name_plural': 'Zmražené výsledkovky'}, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										150
									
								
								seminar/migrations/0138_tvorba_delete.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								seminar/migrations/0138_tvorba_delete.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,150 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 14:03 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('tvorba', '0001_tvorba_create'), | ||||||
|  |         ('seminar', '0137_tvorba_unmanage'), | ||||||
|  |         ('odevzdavatko', '0005_tvorba_relink'), | ||||||
|  |         ('soustredeni', '0009_tvorba_relink5'), | ||||||
|  |         ('various', '0005_tvorba_relink'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='cislo', | ||||||
|  |             name='rocnik', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='clanek', | ||||||
|  |             name='cislo', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='clanek', | ||||||
|  |             name='problem_ptr', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='deadline', | ||||||
|  |             name='cislo', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='pohadka', | ||||||
|  |             name='autor', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='problem', | ||||||
|  |             name='autor', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='problem', | ||||||
|  |             name='garant', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='problem', | ||||||
|  |             name='nadproblem', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='problem', | ||||||
|  |             name='opravovatele', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='problem', | ||||||
|  |             name='polymorphic_ctype', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='problem', | ||||||
|  |             name='zamereni', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='Problemy_Opravovatele', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='tema', | ||||||
|  |             name='problem_ptr', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='tema', | ||||||
|  |             name='rocnik', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='uloha', | ||||||
|  |             name='cislo_deadline', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='uloha', | ||||||
|  |             name='cislo_reseni', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='uloha', | ||||||
|  |             name='cislo_zadani', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='uloha', | ||||||
|  |             name='problem_ptr', | ||||||
|  |         ), | ||||||
|  |         migrations.RemoveField( | ||||||
|  |             model_name='zmrazenavysledkovka', | ||||||
|  |             name='deadline', | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='cislonode', | ||||||
|  |             name='cislo', | ||||||
|  |             field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='tvorba.cislo', verbose_name='číslo'), | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='pohadkanode', | ||||||
|  |             name='pohadka', | ||||||
|  |             field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='tvorba.pohadka', verbose_name='pohádka'), | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='rocniknode', | ||||||
|  |             name='rocnik', | ||||||
|  |             field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='tvorba.rocnik', verbose_name='ročník'), | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='temavcislenode', | ||||||
|  |             name='tema', | ||||||
|  |             field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tvorba.tema', verbose_name='téma v čísle'), | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='ulohavzoraknode', | ||||||
|  |             name='uloha', | ||||||
|  |             field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='tvorba.uloha', verbose_name='úloha'), | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='ulohazadaninode', | ||||||
|  |             name='uloha', | ||||||
|  |             field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='tvorba.uloha', verbose_name='úloha'), | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='Cislo', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='Clanek', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='Deadline', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='Pohadka', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='Problem', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='Rocnik', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='Tema', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='Uloha', | ||||||
|  |         ), | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='ZmrazenaVysledkovka', | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										14
									
								
								seminar/migrations/0139_tvorba_post.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								seminar/migrations/0139_tvorba_post.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 21:35 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('seminar', '0138_tvorba_delete'), | ||||||
|  |         ('tvorba', '0003_tvorba_post'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |     ] | ||||||
|  | @ -9,8 +9,15 @@ from personalni.models import Organizator, Resitel, Skola, Prijemce, Osoba | ||||||
| from soustredeni.models import Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Konfera, Konfery_Ucastnici | from soustredeni.models import Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Konfera, Konfery_Ucastnici | ||||||
| from novinky.models import Novinky | from novinky.models import Novinky | ||||||
| from odevzdavatko.models import Reseni, PrilohaReseni, Reseni_Resitele, Hodnoceni | from odevzdavatko.models import Reseni, PrilohaReseni, Reseni_Resitele, Hodnoceni | ||||||
|  | from tvorba.models import ZmrazenaVysledkovka, Deadline, Cislo, Rocnik, Pohadka, Tema, Problem, Problemy_Opravovatele, Uloha, Clanek | ||||||
| 
 | 
 | ||||||
| # Kvůli migr. 0041 | # Kvůli migr. 0041 | ||||||
| from soustredeni.models import generate_filename_konfera | from soustredeni.models import generate_filename_konfera | ||||||
| # migr. 0001 | # migr. 0001 | ||||||
| from odevzdavatko.models import generate_filename | from odevzdavatko.models import generate_filename | ||||||
|  | # migr. 0031, 0032, 0081 | ||||||
|  | from tvorba.models import cislo_pdf_filename | ||||||
|  | # migr. 0082 | ||||||
|  | from tvorba.models import cislo_png_filename | ||||||
|  | # migr 0100 (hack) | ||||||
|  | import tvorba.models as tvorba | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ from .pomocne import Text | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
| from seminar.models import tvorba as am | import tvorba.models as am | ||||||
| 
 | 
 | ||||||
| class TreeNode(PolymorphicModel): | class TreeNode(PolymorphicModel): | ||||||
| 	class Meta: | 	class Meta: | ||||||
|  |  | ||||||
|  | @ -1,40 +1,7 @@ | ||||||
| import datetime |  | ||||||
| import os | import os | ||||||
| import subprocess |  | ||||||
| import pathlib |  | ||||||
| import tempfile |  | ||||||
| import logging | import logging | ||||||
| 
 | 
 | ||||||
| from django.contrib.sites.shortcuts import get_current_site |  | ||||||
| from django.db import models |  | ||||||
| from django.db.models import Q |  | ||||||
| from django.template.loader import render_to_string |  | ||||||
| from django.utils import timezone |  | ||||||
| from django.conf import settings |  | ||||||
| from django.urls import reverse |  | ||||||
| from django.core.cache import cache |  | ||||||
| from django.core.exceptions import ObjectDoesNotExist, ValidationError |  | ||||||
| from django.core.files.storage import FileSystemStorage | from django.core.files.storage import FileSystemStorage | ||||||
| from django.utils.text import get_valid_filename |  | ||||||
| from django.utils.functional import cached_property |  | ||||||
| 
 |  | ||||||
| from solo.models import SingletonModel |  | ||||||
| from taggit.managers import TaggableManager |  | ||||||
| 
 |  | ||||||
| from reversion import revisions as reversion |  | ||||||
| 
 |  | ||||||
| from tvorba.utils import roman, aktivniResitele |  | ||||||
| from treenode import treelib |  | ||||||
| 
 |  | ||||||
| from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované) |  | ||||||
| 
 |  | ||||||
| from polymorphic.models import PolymorphicModel |  | ||||||
| 
 |  | ||||||
| from django.core.mail import EmailMessage |  | ||||||
| 
 |  | ||||||
| from personalni.models import Prijemce, Organizator |  | ||||||
| 
 |  | ||||||
| from .base import SeminarModelBase |  | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
|  | @ -45,677 +12,3 @@ class OverwriteStorage(FileSystemStorage): | ||||||
| 		if self.exists(name): | 		if self.exists(name): | ||||||
| 			os.remove(os.path.join(self.location,name)) | 			os.remove(os.path.join(self.location,name)) | ||||||
| 		return super().get_available_name(name,max_length) | 		return super().get_available_name(name,max_length) | ||||||
| 
 |  | ||||||
| @reversion.register(ignore_duplicates=True) |  | ||||||
| class Rocnik(SeminarModelBase): |  | ||||||
| 
 |  | ||||||
| 	class Meta: |  | ||||||
| 		db_table = 'seminar_rocniky' |  | ||||||
| 		verbose_name = 'Ročník' |  | ||||||
| 		verbose_name_plural = 'Ročníky' |  | ||||||
| 		ordering = ['-rocnik'] |  | ||||||
| 
 |  | ||||||
| 	# Interní ID |  | ||||||
| 	id = models.AutoField(primary_key = True) |  | ||||||
| 
 |  | ||||||
| 	prvni_rok = models.IntegerField('první rok', db_index=True, unique=True) |  | ||||||
| 
 |  | ||||||
| 	rocnik = models.IntegerField('číslo ročníku', db_index=True, unique=True) |  | ||||||
| 
 |  | ||||||
| 	exportovat = models.BooleanField('export do AESOPa', db_column='exportovat', default=False, |  | ||||||
| 			help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti),' |  | ||||||
| 			' a to jen čísla s veřejnou výsledkovkou') |  | ||||||
| 
 |  | ||||||
| 	# má OneToOneField s: |  | ||||||
| 	# RocnikNode |  | ||||||
| 
 |  | ||||||
| 	def __str__(self): |  | ||||||
| 		return '{} ({}/{})'.format(self.rocnik, self.prvni_rok, self.prvni_rok+1) |  | ||||||
| 
 |  | ||||||
| 	# Ročník v římských číslech |  | ||||||
| 	def roman(self): |  | ||||||
| 		return roman(int(self.rocnik)) |  | ||||||
| 
 |  | ||||||
| 	def verejne(self): |  | ||||||
| 		return len(self.verejna_cisla()) > 0 |  | ||||||
| 	verejne.boolean = True |  | ||||||
| 	verejne.short_description = 'Veřejný (jen dle čísel)' |  | ||||||
| 
 |  | ||||||
| 	def neverejna_cisla(self): |  | ||||||
| 		vc = [c for c in self.cisla.all() if not c.verejne()] |  | ||||||
| 		vc.sort(key=lambda c: c.poradi) |  | ||||||
| 		return vc |  | ||||||
| 
 |  | ||||||
| 	def verejna_cisla(self): |  | ||||||
| 		vc = [c for c in self.cisla.all() if c.verejne()] |  | ||||||
| 		vc.sort(key=lambda c: c.poradi) |  | ||||||
| 		return vc |  | ||||||
| 
 |  | ||||||
| 	def posledni_verejne_cislo(self): |  | ||||||
| 		vc = self.verejna_cisla() |  | ||||||
| 		return vc[-1] if vc else None |  | ||||||
| 
 |  | ||||||
| 	def verejne_vysledkovky_cisla(self): |  | ||||||
| 		vc = list(self.cisla.filter(deadline_v_cisle__verejna_vysledkovka=True).distinct()) |  | ||||||
| 		vc.sort(key=lambda c: c.poradi) |  | ||||||
| 		return vc |  | ||||||
| 
 |  | ||||||
| 	def posledni_zverejnena_vysledkovka_cislo(self): |  | ||||||
| 		vc = self.verejne_vysledkovky_cisla() |  | ||||||
| 		return vc[-1] if vc else None |  | ||||||
| 
 |  | ||||||
| 	def druhy_rok(self): |  | ||||||
| 		return self.prvni_rok + 1 |  | ||||||
| 
 |  | ||||||
| 	def verejne_url(self): |  | ||||||
| 		return reverse('seminar_rocnik', kwargs={'rocnik': self.rocnik}) |  | ||||||
| 
 |  | ||||||
| 	@classmethod |  | ||||||
| 	def cached_rocnik(cls, r_id): |  | ||||||
| 		name = 'rocnik_%s' % (r_id, ) |  | ||||||
| 		c = cache.get(name) |  | ||||||
| 		if c is None: |  | ||||||
| 			c = cls.objects.get(id=r_id) |  | ||||||
| 			cache.set(name, c, 300) |  | ||||||
| 		return c |  | ||||||
| 		 |  | ||||||
| 	def save(self, *args, **kwargs): |  | ||||||
| 		super().save(*args, **kwargs) |  | ||||||
| 		# *Node.save() aktualizuje název *Nodu. |  | ||||||
| 		try: |  | ||||||
| 			self.rocniknode.save() |  | ||||||
| 		except ObjectDoesNotExist: |  | ||||||
| 			# Neexistující *Node nemá smysl aktualizovat. |  | ||||||
| 			pass |  | ||||||
| 
 |  | ||||||
| def cislo_pdf_filename(self, filename): |  | ||||||
| 	rocnik = str(self.rocnik.rocnik) |  | ||||||
| 	return pathlib.Path('cislo', 'pdf', rocnik, '{}-{}.pdf'.format(rocnik, self.poradi)) |  | ||||||
| 
 |  | ||||||
| def cislo_png_filename(self, filename): |  | ||||||
| 	rocnik = str(self.rocnik.rocnik) |  | ||||||
| 	return pathlib.Path('cislo', 'png', rocnik, '{}-{}.png'.format(rocnik, self.poradi)) |  | ||||||
| 
 |  | ||||||
| @reversion.register(ignore_duplicates=True) |  | ||||||
| class Cislo(SeminarModelBase): |  | ||||||
| 
 |  | ||||||
| 	class Meta: |  | ||||||
| 		db_table = 'seminar_cisla' |  | ||||||
| 		verbose_name = 'Číslo' |  | ||||||
| 		verbose_name_plural = 'Čísla' |  | ||||||
| 		ordering = ['-rocnik__rocnik', '-poradi'] |  | ||||||
| 
 |  | ||||||
| 	# Interní ID |  | ||||||
| 	id = models.AutoField(primary_key = True) |  | ||||||
| 
 |  | ||||||
| 	rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='cisla', |  | ||||||
| 		db_index=True,on_delete=models.PROTECT) |  | ||||||
| 
 |  | ||||||
| 	poradi = models.CharField('název čísla', max_length=32, db_index=True, |  | ||||||
| 		help_text='Většinou jen "1", vyjímečně "7-8", lexikograficky určuje pořadí v ročníku!') |  | ||||||
| 
 |  | ||||||
| 	datum_vydani = models.DateField('datum vydání', blank=True, null=True, |  | ||||||
| 		help_text='Datum vydání finální verze') |  | ||||||
| 
 |  | ||||||
| 	verejne_db = models.BooleanField('číslo zveřejněno', |  | ||||||
| 		db_column='verejne', default=False) |  | ||||||
| 
 |  | ||||||
| 	poznamka = models.TextField('neveřejná poznámka', blank=True, |  | ||||||
| 		help_text='Neveřejná poznámka k číslu (plain text)') |  | ||||||
| 
 |  | ||||||
| 	pdf = models.FileField('pdf', upload_to=cislo_pdf_filename, null=True, blank=True, |  | ||||||
| 		help_text='PDF čísla, které si mohou řešitelé stáhnout', storage=OverwriteStorage()) |  | ||||||
| 
 |  | ||||||
| 	titulka_nahled = models.ImageField('Obrázek titulní strany', upload_to=cislo_png_filename, null=True, blank=True, |  | ||||||
| 		help_text='Obrázek titulní strany, generuje se automaticky') |  | ||||||
| 
 |  | ||||||
| 	# má OneToOneField s: |  | ||||||
| 	# CisloNode |  | ||||||
| 
 |  | ||||||
| 	def kod(self): |  | ||||||
| 		return '%s.%s' % (self.rocnik.rocnik, self.poradi) |  | ||||||
| 	kod.short_description = 'Kód čísla' |  | ||||||
| 
 |  | ||||||
| 	def __str__(self): |  | ||||||
| 		# Potenciální DB HOG, pokud by se ročník necachoval |  | ||||||
| 		r = Rocnik.cached_rocnik(self.rocnik_id) |  | ||||||
| 		return '{}.{}'.format(r.rocnik, self.poradi) |  | ||||||
| 
 |  | ||||||
| 	def verejne(self): |  | ||||||
| 		return self.verejne_db |  | ||||||
| 	verejne.boolean = True |  | ||||||
| 
 |  | ||||||
| 	def verejne_url(self): |  | ||||||
| 		return reverse('seminar_cislo', kwargs={'rocnik': self.rocnik.rocnik, 'cislo': self.poradi}) |  | ||||||
| 
 |  | ||||||
| 	def absolute_url(self): |  | ||||||
| 		return "https://" + str(get_current_site(None)) + self.verejne_url() |  | ||||||
| 
 |  | ||||||
| 	def nasledujici(self): |  | ||||||
| 		"Vrací None, pokud je toto poslední" |  | ||||||
| 		return self.relativni_v_rocniku(1) |  | ||||||
| 
 |  | ||||||
| 	def predchozi(self): |  | ||||||
| 		"Vrací None, pokud je toto první" |  | ||||||
| 		return self.relativni_v_rocniku(-1) |  | ||||||
| 
 |  | ||||||
| 	def relativni_v_rocniku(self, rel_index): |  | ||||||
| 		"Číslo o `index` dále v ročníku. None pokud neexistuje." |  | ||||||
| 		cs = self.rocnik.cisla.order_by('poradi').all() |  | ||||||
| 		i = list(cs).index(self) + rel_index |  | ||||||
| 		if (i < 0) or (i >= len(cs)): |  | ||||||
| 			return None |  | ||||||
| 		return cs[i] |  | ||||||
| 
 |  | ||||||
| 	def vygeneruj_nahled(self): |  | ||||||
| 		VYSKA = 594 |  | ||||||
| 		sirka = int(VYSKA*210/297) |  | ||||||
| 		if not self.pdf: |  | ||||||
| 			return |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 		# Pokud obrázek neexistuje nebo není aktuální, vytvoř jej |  | ||||||
| 		if not self.titulka_nahled or os.path.getmtime(self.titulka_nahled.path) < os.path.getmtime(self.pdf.path): |  | ||||||
| 			png_filename = pathlib.Path(tempfile.mkdtemp(), 'nahled.png') |  | ||||||
| 
 |  | ||||||
| 			subprocess.run([ |  | ||||||
| 				"gs", |  | ||||||
| 				"-sstdout=%stderr", |  | ||||||
| 				"-dSAFER", |  | ||||||
| 				"-dNOPAUSE", |  | ||||||
| 				"-dBATCH", |  | ||||||
| 				"-dNOPROMPT", |  | ||||||
| 				"-sDEVICE=png16m", |  | ||||||
| 				"-r300x300", |  | ||||||
| 				"-dFirstPage=1d", |  | ||||||
| 				"-dLastPage=1d", |  | ||||||
| 				"-sOutputFile=" + str(png_filename), |  | ||||||
| 				"-f%s" % self.pdf.path |  | ||||||
| 				], |  | ||||||
| 				check=True, |  | ||||||
| 				capture_output=True |  | ||||||
| 			) |  | ||||||
| 
 |  | ||||||
| 			with open(png_filename,'rb') as f: |  | ||||||
| 				self.titulka_nahled.save('',f,True) |  | ||||||
| 
 |  | ||||||
| 			png_filename.unlink() |  | ||||||
| 			png_filename.parent.rmdir() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 	@classmethod |  | ||||||
| 	def get(cls, rocnik, cislo): |  | ||||||
| 		try: |  | ||||||
| 			r = Rocnik.objects.get(rocnik=rocnik) |  | ||||||
| 			c = r.cisla.get(poradi=cislo) |  | ||||||
| 		except ObjectDoesNotExist: |  | ||||||
| 			return None |  | ||||||
| 		return c |  | ||||||
| 
 |  | ||||||
| 	def __init__(self, *args, **kwargs): |  | ||||||
| 		super().__init__(*args, **kwargs) |  | ||||||
| 		self.__original_verejne = self.verejne_db |  | ||||||
| 
 |  | ||||||
| 	def posli_cislo_mailem(self): |  | ||||||
| 		# parametry e-mailu |  | ||||||
| 		odkaz = self.absolute_url() |  | ||||||
| 
 |  | ||||||
| 		poslat_z_mailu = 'zadani@mam.mff.cuni.cz' |  | ||||||
| 		predmet = 'Vyšlo číslo {}'.format(self.kod()) |  | ||||||
| 		# TODO Možná nechceme všem psát „Ahoj“, např. příjemcům… |  | ||||||
| 		text_mailu = 'Ahoj,\n' \ |  | ||||||
| 			   'na adrese {} najdete nejnovější číslo.\n' \ |  | ||||||
| 			   'Vaše M&M\n'.format(odkaz) |  | ||||||
| 
 |  | ||||||
| 		predmet_prvni = 'Právě vyšlo 1. číslo M&M, pomoz nám ho poslat dál!' |  | ||||||
| 		text_mailu_prvni = 'Milý řešiteli,\n'\ |  | ||||||
| 			'právě jsme na našem webu zveřejnili první číslo {}. ročníku, najdeš ho na tomto odkazu: {}.\n\n'\ |  | ||||||
| 			'Doufáme, že tě M&M baví, a byli bychom rádi, kdyby mohlo dělat radost i dalším středoškolákům. Máme na tebe proto jednu prosbu. Sdílej prosím odkaz alespoň s jedním svým kamarádem, který by mohl mít o řešení M&M zájem. Je to pro nás moc důležité a velmi nám tím pomůžeš. Díky!\n\n'\ |  | ||||||
| 			'Organizátoři M&M\n'.format(self.rocnik.rocnik, odkaz) |  | ||||||
| 
 |  | ||||||
| 		predmet_resitel = predmet_prvni if self.poradi == "1" else predmet |  | ||||||
| 		text_mailu_resitel = text_mailu_prvni if self.poradi == "1" else text_mailu |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 		# Prijemci e-mailu |  | ||||||
| 		resitele_vsichni = aktivniResitele(self).filter(zasilat_cislo_emailem=True) |  | ||||||
| 
 |  | ||||||
| 		def posli(subject, text, resitele): |  | ||||||
| 			emaily = map(lambda resitel: resitel.osoba.email, resitele) |  | ||||||
| 
 |  | ||||||
| 			email = EmailMessage( |  | ||||||
| 				subject=subject, |  | ||||||
| 				body=text, |  | ||||||
| 				from_email=poslat_z_mailu, |  | ||||||
| 				bcc=list(emaily) |  | ||||||
| 				#bcc = příjemci skryté kopie |  | ||||||
| 			) |  | ||||||
| 
 |  | ||||||
| 			email.send() |  | ||||||
| 
 |  | ||||||
| 		paticka = "---\nK odběru těchto e-mailů jste se přihlásili na stránkách https://mam.matfyz.cz. Z odběru se lze odhlásit na https://mam.matfyz.cz/resitel/osobni-udaje/" |  | ||||||
| 
 |  | ||||||
| 		posli(predmet_resitel, text_mailu_resitel + paticka, resitele_vsichni.filter(zasilat_cislo_papirove=False)) |  | ||||||
| 		posli(predmet_resitel, text_mailu_resitel + 'P. S. Brzy budeme též rozesílat papírovou verzi čísla. Připomínáme, že pokud papírovou verzi čísla nevyužijete, můžete v https://mam.mff.cuni.cz/resitel/osobni-udaje/ zaškrtnout, abychom vám ji neposílali. Čísla vždy můžete nalézt v našem archivu a dál vám budou chodit e-mailem. Děkujeme.\n' + paticka, |  | ||||||
| 			  resitele_vsichni.filter(zasilat_cislo_papirove=True)) |  | ||||||
| 
 |  | ||||||
| 		paticka_prijemce = "---\nPokud tyto e-maily nechcete nadále dostávat, prosíme, ozvěte se nám na mam@matfyz.cz." |  | ||||||
| 		posli(predmet, text_mailu + paticka_prijemce, Prijemce.objects.filter(zasilat_cislo_emailem=True)) |  | ||||||
| 
 |  | ||||||
| 	def save(self, *args, **kwargs): |  | ||||||
| 		super().save(*args, **kwargs) |  | ||||||
| 		self.vygeneruj_nahled() |  | ||||||
| 		# Při zveřejnění pošle mail |  | ||||||
| 		if self.verejne_db and not self.__original_verejne: |  | ||||||
| 			self.posli_cislo_mailem() |  | ||||||
| 		# *Node.save() aktualizuje název *Nodu. |  | ||||||
| 		try: |  | ||||||
| 			self.cislonode.save() |  | ||||||
| 		except ObjectDoesNotExist: |  | ||||||
| 			# Neexistující *Node nemá smysl aktualizovat, ale je potřeba ho naopak vyrobit |  | ||||||
| 			logger.warning(f'Číslo {self} nemělo ČísloNode, vyrábím…') |  | ||||||
| 			from seminar.models.treenode import CisloNode |  | ||||||
| 			CisloNode.objects.create(cislo=self) |  | ||||||
| 
 |  | ||||||
| 	def zlomovy_deadline_pro_papirove_cislo(self): |  | ||||||
| 		prvni_deadline = Deadline.objects.filter(Q(typ=Deadline.TYP_PRVNI) | Q(typ=Deadline.TYP_PRVNI_A_SOUS), cislo=self).first() |  | ||||||
| 		if prvni_deadline is None: |  | ||||||
| 			posledni_deadline = self.posledni_deadline |  | ||||||
| 			if posledni_deadline is None: |  | ||||||
| 				# TODO promyslet, co se má stát tady |  | ||||||
| 				return Deadline.objects.filter(Q(cislo__poradi__lt=self.poradi, cislo__rocnik=self.rocnik) | Q(cislo__rocnik__rocnik__lt=self.rocnik.rocnik)).order_by("deadline").last() |  | ||||||
| 			return posledni_deadline |  | ||||||
| 		return prvni_deadline |  | ||||||
| 
 |  | ||||||
| 	@property |  | ||||||
| 	def posledni_deadline(self): |  | ||||||
| 		return self.deadline_v_cisle.all().order_by("deadline").last() |  | ||||||
| 
 |  | ||||||
| class Deadline(SeminarModelBase): |  | ||||||
| 	class Meta: |  | ||||||
| 		db_table = 'seminar_deadliny' |  | ||||||
| 		verbose_name = 'Deadline' |  | ||||||
| 		verbose_name_plural = 'Deadliny' |  | ||||||
| 		ordering = ['deadline'] |  | ||||||
| 
 |  | ||||||
| 	def __init__(self, *args, **kwargs): |  | ||||||
| 		super().__init__(*args, **kwargs) |  | ||||||
| 		self.__original_verejna_vysledkovka = self.verejna_vysledkovka |  | ||||||
| 
 |  | ||||||
| 	id = models.AutoField(primary_key=True) |  | ||||||
| 
 |  | ||||||
| 	# V ročníku < 26 nastaveno na datetime.datetime.combine(datetime.date(1994 + cislo.rocnik.rocnik, 6, int(cislo.poradi[0])), datetime.time.min) |  | ||||||
| 	deadline = models.DateTimeField(blank=False, default=timezone.make_aware(datetime.datetime.combine(timezone.now(), datetime.time.max))) |  | ||||||
| 
 |  | ||||||
| 	cislo = models.ForeignKey(Cislo, verbose_name='deadline v čísle', |  | ||||||
| 		related_name='deadline_v_cisle', blank=False, |  | ||||||
| 		on_delete=models.CASCADE) |  | ||||||
| 
 |  | ||||||
| 	TYP_CISLA = 'cisla' |  | ||||||
| 	TYP_PRVNI_A_SOUS = 'prvniasous' |  | ||||||
| 	TYP_PRVNI = 'prvni' |  | ||||||
| 	TYP_SOUS = 'sous' |  | ||||||
| 	TYP_CHOICES = [ |  | ||||||
| 		(TYP_CISLA, 'Deadline celého čísla'), |  | ||||||
| 		(TYP_PRVNI, 'První deadline'), |  | ||||||
| 		(TYP_PRVNI_A_SOUS, 'Sousový a první deadline'), |  | ||||||
| 		(TYP_SOUS, 'Sousový deadline'), |  | ||||||
| 	] |  | ||||||
| 	CHOICES_MAP = dict(TYP_CHOICES) |  | ||||||
| 	typ = models.CharField('typ deadlinu', max_length=32, |  | ||||||
| 							choices=TYP_CHOICES, blank=False) |  | ||||||
| 
 |  | ||||||
| 	verejna_vysledkovka = models.BooleanField('veřejná výsledkovka', |  | ||||||
| 											  db_column='verejna_vysledkovka', |  | ||||||
| 											  default=False) |  | ||||||
| 
 |  | ||||||
| 	def __str__(self): |  | ||||||
| 		return self.CHOICES_MAP[self.typ] + " " + str(self.cislo) |  | ||||||
| 
 |  | ||||||
| 	def save(self, *args, **kwargs): |  | ||||||
| 		super().save(*args, **kwargs) |  | ||||||
| 		if self.verejna_vysledkovka and not self.__original_verejna_vysledkovka: |  | ||||||
| 			self.vygeneruj_vysledkovku() |  | ||||||
| 		if not self.verejna_vysledkovka and hasattr(self, "vysledkovka_v_deadlinu"): |  | ||||||
| 			self.vysledkovka_v_deadlinu.delete() |  | ||||||
| 
 |  | ||||||
| 	def vygeneruj_vysledkovku(self): |  | ||||||
| 		from vysledkovky.utils import VysledkovkaCisla |  | ||||||
| 		if hasattr(self, "vysledkovka_v_deadlinu"): |  | ||||||
| 			self.vysledkovka_v_deadlinu.delete() |  | ||||||
| 		vysledkovka = VysledkovkaCisla(self.cislo, jen_verejne=True, do_deadlinu=self) |  | ||||||
| 		if len(vysledkovka.radky_vysledkovky) != 0: |  | ||||||
| 			ZmrazenaVysledkovka.objects.create( |  | ||||||
| 				deadline=self, |  | ||||||
| 				html=render_to_string( |  | ||||||
| 					"vysledkovky/vysledkovka_cisla.html", |  | ||||||
| 					context={"vysledkovka": vysledkovka, "oznaceni_vysledkovky": self.id} |  | ||||||
| 				) |  | ||||||
| 			) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ZmrazenaVysledkovka(SeminarModelBase): |  | ||||||
| 	class Meta: |  | ||||||
| 		db_table = 'seminar_vysledkovky' |  | ||||||
| 		verbose_name = 'Zmražená výsledkovka' |  | ||||||
| 		verbose_name_plural = 'Zmražené výsledkovky' |  | ||||||
| 
 |  | ||||||
| 	deadline = models.OneToOneField( |  | ||||||
| 		Deadline, |  | ||||||
| 		on_delete=models.CASCADE, |  | ||||||
| 		primary_key=True, |  | ||||||
| 		related_name="vysledkovka_v_deadlinu" |  | ||||||
| 	) |  | ||||||
| 
 |  | ||||||
| 	html = models.TextField(null=False, blank=False) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @reversion.register(ignore_duplicates=True) |  | ||||||
| # Pozor na následující řádek. *Nekrmit, asi kouše!* |  | ||||||
| class Problem(SeminarModelBase,PolymorphicModel): |  | ||||||
| 
 |  | ||||||
| 	class Meta: |  | ||||||
| 		# Není abstraktní, protože se na něj jinak nedají dělat ForeignKeys. |  | ||||||
| 		# TODO: Udělat to polymorfní (pomocí django-polymorphic), abychom dostali  |  | ||||||
| 		# po těch vazbách přímo tu úlohu/témátko vč. fieldů, které nejsou součástí  |  | ||||||
| 		# modelu Problem? |  | ||||||
| 
 |  | ||||||
| 		#abstract = True |  | ||||||
| 		db_table = 'seminar_problemy' |  | ||||||
| 		verbose_name = 'Problém' |  | ||||||
| 		verbose_name_plural = 'Problémy' |  | ||||||
| 		ordering = ['nazev'] |  | ||||||
| 
 |  | ||||||
| 	# Interní ID |  | ||||||
| 	id = models.AutoField(primary_key = True) |  | ||||||
| 
 |  | ||||||
| 	# Název |  | ||||||
| 	nazev = models.CharField('název', max_length=256) # Zveřejnitelný na stránky |  | ||||||
| 
 |  | ||||||
| 	# Problém má podproblémy |  | ||||||
| 	nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém', |  | ||||||
| 		related_name='podproblem', null=True, blank=True, |  | ||||||
| 		on_delete=models.SET_NULL) |  | ||||||
| 
 |  | ||||||
| 	STAV_NAVRH = 'navrh' |  | ||||||
| 	STAV_ZADANY = 'zadany' |  | ||||||
| 	STAV_VYRESENY = 'vyreseny' |  | ||||||
| 	STAV_SMAZANY = 'smazany' |  | ||||||
| 	STAV_CHOICES = [ |  | ||||||
| 		(STAV_NAVRH, 'Návrh'), |  | ||||||
| 		(STAV_ZADANY, 'Zadaný'), |  | ||||||
| 		(STAV_VYRESENY, 'Vyřešený'), |  | ||||||
| 		(STAV_SMAZANY, 'Smazaný'), |  | ||||||
| 		] |  | ||||||
| 	stav = models.CharField('stav problému', max_length=32, choices=STAV_CHOICES, blank=False, default=STAV_NAVRH) |  | ||||||
| 	# Téma je taky Problém, takže má stavy, "zadané" témátko je aktuálně otevřené a dá se k němu něco poslat (řešení nebo článek) |  | ||||||
| 
 |  | ||||||
| 	zamereni = TaggableManager(verbose_name='zaměření',  |  | ||||||
| 		help_text='Zaměření M/F/I/O problému, příp. další tagy', blank=True) |  | ||||||
| 
 |  | ||||||
| 	poznamka = models.TextField('org poznámky (HTML)', blank=True, |  | ||||||
| 		help_text='Neveřejný návrh úlohy, návrh řešení, text zadání, poznámky ...') |  | ||||||
| 
 |  | ||||||
| 	autor = models.ForeignKey(Organizator, verbose_name='autor problému', |  | ||||||
| 		related_name='autor_problemu_%(class)s', null=True, blank=True, |  | ||||||
| 		on_delete=models.SET_NULL) |  | ||||||
| 
 |  | ||||||
| 	garant = models.ForeignKey(Organizator, verbose_name='garant zadaného problému', |  | ||||||
| 		related_name='garant_problemu_%(class)s', null=True, blank=True, |  | ||||||
| 		on_delete=models.SET_NULL) |  | ||||||
| 
 |  | ||||||
| 	opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé', |  | ||||||
| 		blank=True, related_name='opravovatele_%(class)s') |  | ||||||
| 
 |  | ||||||
| 	kod = models.CharField('lokální kód', max_length=32, blank=True, default='', |  | ||||||
| 		help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku') |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 	vytvoreno = models.DateTimeField('vytvořeno', default=timezone.now, blank=True, editable=False) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 	def __str__(self): |  | ||||||
| 		return self.nazev |  | ||||||
| 
 |  | ||||||
| 	# Implicitini implementace, jednotlivé dědící třídy si přepíšou |  | ||||||
| 	@cached_property |  | ||||||
| 	def kod_v_rocniku(self): |  | ||||||
| 		if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY: |  | ||||||
| 			if self.nadproblem: |  | ||||||
| 				return self.nadproblem.kod_v_rocniku+".{}".format(self.kod) |  | ||||||
| 			return str(self.kod) |  | ||||||
| 		logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.") |  | ||||||
| 		return f'<Není zadaný: {self.kod}>' |  | ||||||
| 
 |  | ||||||
| #	def verejne(self): |  | ||||||
| #		# aktuálně podle stavu problému |  | ||||||
| #		# FIXME pro některé problémy možná chceme override |  | ||||||
| #		# FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je.  |  | ||||||
| #		# Je to tak správně? Podle aktuální představy ano. |  | ||||||
| #		stav_verejny = False |  | ||||||
| #		if self.stav == 'zadany' or self.stav == 'vyreseny': |  | ||||||
| #			stav_verejny = True |  | ||||||
| #			print("stav_verejny: {}".format(stav_verejny))		 |  | ||||||
| # |  | ||||||
| #		cislo_verejne = False |  | ||||||
| #		cislonode = self.cislo_node() |  | ||||||
| #		if cislonode is None: |  | ||||||
| #			# problém nemá vlastní node, veřejnost posuzujeme jen podle stavu |  | ||||||
| #			print("empty node")		 |  | ||||||
| #			return stav_verejny |  | ||||||
| #		else:	 |  | ||||||
| #			cislo_zadani = cislonode.cislo |  | ||||||
| #			if (cislo_zadani and cislo_zadani.verejne()): |  | ||||||
| #				print("cislo: {}".format(cislo_zadani)) |  | ||||||
| #				cislo_verejne = True |  | ||||||
| #			print("stav_verejny: {}".format(stav_verejny))		 |  | ||||||
| #			print("cislo_verejne: {}".format(cislo_verejne))		 |  | ||||||
| #			return (stav_verejny and cislo_verejne) |  | ||||||
| #	verejne.boolean = True |  | ||||||
| 
 |  | ||||||
| 	def verejne_url(self): |  | ||||||
| 		return reverse('seminar_problem', kwargs={'pk': self.id}) |  | ||||||
| 
 |  | ||||||
| 	def admin_url(self): |  | ||||||
| 			return reverse('admin:seminar_problem_change', args=(self.id, )) |  | ||||||
| 
 |  | ||||||
| 	@cached_property |  | ||||||
| 	def hlavni_problem(self): |  | ||||||
| 		""" Pro daný problém vrátí jeho nejvyšší nadproblém.""" |  | ||||||
| 		problem = self |  | ||||||
| 		while not (problem.nadproblem is None): |  | ||||||
| 			problem = problem.nadproblem |  | ||||||
| 		return problem |  | ||||||
| 
 |  | ||||||
| # FIXME - k úloze |  | ||||||
| 	def body_v_zavorce(self): |  | ||||||
| 		"""Vrať string s body v závorce jsou-li u problému vyplněné, jinak '' |  | ||||||
| 
 |  | ||||||
| 		Je-li desetinná část nulová, nezobrazuj ji. |  | ||||||
| 		""" |  | ||||||
| 		pocet_bodu = None |  | ||||||
| 		if self.body: |  | ||||||
| 			b = self.body |  | ||||||
| 			pocet_bodu = int(b) if int(b) == b else b |  | ||||||
| 		return "({}\u2009b)".format(pocet_bodu) if self.body else "" |  | ||||||
| 
 |  | ||||||
| class Tema(Problem): |  | ||||||
| 	class Meta: |  | ||||||
| 		db_table = 'seminar_temata' |  | ||||||
| 		verbose_name = 'Téma' |  | ||||||
| 		verbose_name_plural = 'Témata' |  | ||||||
| 	 |  | ||||||
| 	TEMA_TEMA = 'tema' |  | ||||||
| 	TEMA_SERIAL = 'serial' |  | ||||||
| 	TEMA_CHOICES = [ |  | ||||||
| 		(TEMA_TEMA, 'Téma'), |  | ||||||
| 		(TEMA_SERIAL, 'Seriál'), |  | ||||||
| 		] |  | ||||||
| 	tema_typ = models.CharField('Typ tématu', max_length=16, choices=TEMA_CHOICES,  |  | ||||||
| 		blank=False, default=TEMA_TEMA) |  | ||||||
| 
 |  | ||||||
| 	rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',related_name='temata',blank=True, null=True, |  | ||||||
| 		on_delete=models.PROTECT) |  | ||||||
| 
 |  | ||||||
| 	abstrakt = models.TextField('Abstrakt na rozcestník', blank=True) |  | ||||||
| 	obrazek = models.ImageField('Obrázek na rozcestník', null=True, blank=True) |  | ||||||
| 
 |  | ||||||
| 	@cached_property |  | ||||||
| 	def kod_v_rocniku(self): |  | ||||||
| 		if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY: |  | ||||||
| 			if self.nadproblem: |  | ||||||
| 				return self.nadproblem.kod_v_rocniku+".t{}".format(self.kod) |  | ||||||
| 			return 't'+self.kod |  | ||||||
| 		logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.") |  | ||||||
| 		return f'<Není zadaný: {self.kod}>' |  | ||||||
| 
 |  | ||||||
| 	def save(self, *args, **kwargs): |  | ||||||
| 		super().save(*args, **kwargs) |  | ||||||
| 		# *Node.save() aktualizuje název *Nodu. |  | ||||||
| 		for tvcn in self.temavcislenode_set.all(): |  | ||||||
| 			tvcn.save() |  | ||||||
| 
 |  | ||||||
| 	def cislo_node(self): |  | ||||||
| 		tema_node_set = self.temavcislenode_set.all() |  | ||||||
| 		tema_cisla_vyskyt = [] |  | ||||||
| 		from seminar.models.treenode import CisloNode |  | ||||||
| 		for tn in tema_node_set: |  | ||||||
| 			tema_cisla_vyskyt.append( |  | ||||||
| 				treelib.get_upper_node_of_type(tn, CisloNode).cislo) |  | ||||||
| 		tema_cisla_vyskyt.sort(key=lambda x:x.datum_vydani) |  | ||||||
| 		prvni_zadani = tema_cisla_vyskyt[0] |  | ||||||
| 		return prvni_zadani.cislonode |  | ||||||
| 
 |  | ||||||
| class Clanek(Problem): |  | ||||||
| 	class Meta: |  | ||||||
| 		db_table = 'seminar_clanky' |  | ||||||
| 		verbose_name = 'Článek' |  | ||||||
| 		verbose_name_plural = 'Články' |  | ||||||
| 	 |  | ||||||
| 	cislo = models.ForeignKey(Cislo, blank=True, null=True, on_delete=models.PROTECT, |  | ||||||
| 		verbose_name='číslo vydání', related_name='vydane_clanky') |  | ||||||
| 	 |  | ||||||
| 	strana = models.PositiveIntegerField(verbose_name="první strana", blank=True, null=True) |  | ||||||
| 
 |  | ||||||
| 	@cached_property |  | ||||||
| 	def kod_v_rocniku(self): |  | ||||||
| 		if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY: |  | ||||||
| # Nemělo by být potřeba |  | ||||||
| #			if self.nadproblem: |  | ||||||
| #				return self.nadproblem.kod_v_rocniku+".c{}".format(self.kod) |  | ||||||
| 			return "c" + self.kod |  | ||||||
| 		logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.") |  | ||||||
| 		return f'<Není zadaný: {self.kod}>' |  | ||||||
| 	 |  | ||||||
| 	def node(self): |  | ||||||
| 		return None |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Uloha(Problem): |  | ||||||
| 	class Meta: |  | ||||||
| 		db_table = 'seminar_ulohy' |  | ||||||
| 		verbose_name = 'Úloha' |  | ||||||
| 		verbose_name_plural = 'Úlohy' |  | ||||||
| 	 |  | ||||||
| 	cislo_zadani = models.ForeignKey(Cislo, verbose_name='číslo zadání', blank=True,  |  | ||||||
| 		null=True, related_name='zadane_ulohy', on_delete=models.PROTECT) |  | ||||||
| 	 |  | ||||||
| 	cislo_deadline = models.ForeignKey(Cislo, verbose_name='číslo deadlinu', blank=True,  |  | ||||||
| 		null=True, related_name='deadlinove_ulohy', on_delete=models.PROTECT) |  | ||||||
| 
 |  | ||||||
| 	cislo_reseni = models.ForeignKey(Cislo, verbose_name='číslo řešení', blank=True,  |  | ||||||
| 		null=True, related_name='resene_ulohy', |  | ||||||
| 		help_text='Číslo s řešením úlohy, jen pro úlohy', |  | ||||||
| 		on_delete=models.PROTECT) |  | ||||||
| 
 |  | ||||||
| 	max_body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='maximum bodů',  |  | ||||||
| 		blank=True, null=True) |  | ||||||
| 
 |  | ||||||
| 	# má OneToOneField s: |  | ||||||
| 	# UlohaZadaniNode |  | ||||||
| 	# UlohaVzorakNode |  | ||||||
| 
 |  | ||||||
| 	@cached_property |  | ||||||
| 	def kod_v_rocniku(self): |  | ||||||
| 		if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY: |  | ||||||
| 			return f"{self.cislo_zadani.poradi}.{self.kod}" |  | ||||||
| 		logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.") |  | ||||||
| 		return f'<Není zadaný: {self.kod}>' |  | ||||||
| 
 |  | ||||||
| 	def save(self, *args, **kwargs): |  | ||||||
| 		super().save(*args, **kwargs) |  | ||||||
| 		# *Node.save() aktualizuje název *Nodu. |  | ||||||
| 		try: |  | ||||||
| 			self.ulohazadaninode.save() |  | ||||||
| 		except ObjectDoesNotExist: |  | ||||||
| 			# Neexistující *Node nemá smysl aktualizovat. |  | ||||||
| 			pass |  | ||||||
| 		try: |  | ||||||
| 			self.ulohavzoraknode.save() |  | ||||||
| 		except ObjectDoesNotExist: |  | ||||||
| 			# Neexistující *Node nemá smysl aktualizovat. |  | ||||||
| 			pass |  | ||||||
| 
 |  | ||||||
| 	def cislo_node(self): |  | ||||||
| 		zadani_node = self.ulohazadaninode |  | ||||||
| 		from seminar.models.treenode import CisloNode |  | ||||||
| 		return treelib.get_upper_node_of_type(zadani_node, CisloNode) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 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) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Pohadka(SeminarModelBase): |  | ||||||
| 	"""Kus pohádky před/za úlohou v čísle""" |  | ||||||
| 
 |  | ||||||
| 	class Meta: |  | ||||||
| 		db_table = 'seminar_pohadky' |  | ||||||
| 		verbose_name = 'Pohádka' |  | ||||||
| 		verbose_name_plural = 'Pohádky' |  | ||||||
| 		ordering = ['vytvoreno'] |  | ||||||
| 
 |  | ||||||
| 	# Interní ID |  | ||||||
| 	id = models.AutoField(primary_key=True) |  | ||||||
| 
 |  | ||||||
| 	autor = models.ForeignKey( |  | ||||||
| 		Organizator, |  | ||||||
| 		verbose_name="Autor pohádky", |  | ||||||
| 
 |  | ||||||
| 		# Při nahrávání z TeXu není vyplnění vyžadováno, v adminu je |  | ||||||
| 		null=True, |  | ||||||
| 		blank=False, |  | ||||||
| 		on_delete=models.SET_NULL |  | ||||||
| 	) |  | ||||||
| 
 |  | ||||||
| 	vytvoreno = models.DateTimeField( |  | ||||||
| 		'Vytvořeno', |  | ||||||
| 		default=timezone.now, |  | ||||||
| 		blank=True, |  | ||||||
| 		editable=False |  | ||||||
| 	) |  | ||||||
| 
 |  | ||||||
| 	# má OneToOneField s: |  | ||||||
| 	# PohadkaNode |  | ||||||
| 
 |  | ||||||
| 	def __str__(self): |  | ||||||
| 		uryvek = self.text if len(self.text) < 50 else self.text[:(50-3)]+"..." |  | ||||||
| 		return uryvek |  | ||||||
| 
 |  | ||||||
| 	def save(self, *args, **kwargs): |  | ||||||
| 		super().save(*args, **kwargs) |  | ||||||
| 		# *Node.save() aktualizuje název *Nodu. |  | ||||||
| 		try: |  | ||||||
| 			self.pohadkanode.save() |  | ||||||
| 		except ObjectDoesNotExist: |  | ||||||
| 			# Neexistující *Node nemá smysl aktualizovat. |  | ||||||
| 			pass |  | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								soustredeni/migrations/0004_tvorba_pre.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								soustredeni/migrations/0004_tvorba_pre.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 01:07 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('soustredeni', '0003_post_split_soustredeni'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |     ] | ||||||
							
								
								
									
										28
									
								
								soustredeni/migrations/0005_tvorba_relink.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								soustredeni/migrations/0005_tvorba_relink.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 13:18 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('tvorba', '0001_tvorba_create'), | ||||||
|  |         ('soustredeni', '0004_tvorba_pre'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         ## Konferu zmigrujeme jinak, kvůli <https://code.djangoproject.com/ticket/23521> jí nejde přepsat někde ve stavu `bases`. | ||||||
|  |         ## Proto si ji unmanagujeme a vyrobíme celou znovu, to by nemělo vadit (zvlášť když t.č. v DB žádná instance Konfery není). | ||||||
|  |         ## (Šlo by `SeparateStateAndData`, což v principu děláme taky ale ty migrace jsou lehce čitelnější a o poznání konzistentnější.) | ||||||
|  |         #migrations.AlterField( | ||||||
|  |         #    model_name='konfera', | ||||||
|  |         #    name='problem_ptr', | ||||||
|  |         #    field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tvorba.problem'), | ||||||
|  |         #), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='soustredeni', | ||||||
|  |             name='rocnik', | ||||||
|  |             field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='soustredeni', to='tvorba.rocnik', verbose_name='ročník'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										17
									
								
								soustredeni/migrations/0006_tvorba_relink2.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								soustredeni/migrations/0006_tvorba_relink2.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 19:37 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('soustredeni', '0005_tvorba_relink'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='konfera', | ||||||
|  |             options={'managed': False, 'verbose_name': 'Konfera', 'verbose_name_plural': 'Konfery'}, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										15
									
								
								soustredeni/migrations/0007_tvorba_relink3.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								soustredeni/migrations/0007_tvorba_relink3.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 19:38 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('soustredeni', '0006_tvorba_relink2'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.DeleteModel( | ||||||
|  |             name='Konfera', | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										34
									
								
								soustredeni/migrations/0008_tvorba_relink4.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								soustredeni/migrations/0008_tvorba_relink4.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 19:45 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations,models | ||||||
|  | import django.db.models.deletion | ||||||
|  | import soustredeni.models | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('soustredeni', '0007_tvorba_relink3'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Konfera', | ||||||
|  |             fields=[ | ||||||
|  |                 ('problem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tvorba.problem')), | ||||||
|  |                 ('anotace', models.TextField(blank=True, help_text='Popis, o čem bude konfera.', verbose_name='anotace')), | ||||||
|  |                 ('abstrakt', models.TextField(blank=True, help_text='Abstrakt konfery tak, jak byl uveden ve sborníku', verbose_name='abstrakt')), | ||||||
|  |                 ('typ_prezentace', models.CharField(choices=[('veletrh', 'Veletrh (postery)'), ('prezentace', 'Prezentace (přednáška)')], default='veletrh', max_length=16, verbose_name='typ prezentace')), | ||||||
|  |                 ('prezentace', models.FileField(blank=True, help_text='Prezentace nebo fotka posteru', upload_to=soustredeni.models.generate_filename_konfera, verbose_name='prezentace')), | ||||||
|  |                 ('materialy', models.FileField(blank=True, help_text='Další materiály ke konfeře zabalené do jednoho souboru', upload_to=soustredeni.models.generate_filename_konfera, verbose_name='materialy')), | ||||||
|  |                 ('soustredeni', models.ForeignKey(to='soustredeni.soustredeni', verbose_name='soustředění', on_delete=models.SET_NULL, null=True, related_name='konfery')), | ||||||
|  |                 ('ucastnici', models.ManyToManyField(help_text='Seznam účastníků konfery', through='soustredeni.Konfery_Ucastnici', to='personalni.resitel', verbose_name='účastníci konfery')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Konfera', | ||||||
|  |                 'verbose_name_plural': 'Konfery', | ||||||
|  |                 'db_table': 'seminar_konfera', | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |             bases=('tvorba.problem',), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										17
									
								
								soustredeni/migrations/0009_tvorba_relink5.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								soustredeni/migrations/0009_tvorba_relink5.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 20:03 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('soustredeni', '0008_tvorba_relink4'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='konfera', | ||||||
|  |             options={'verbose_name': 'Konfera', 'verbose_name_plural': 'Konfery'}, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										14
									
								
								soustredeni/migrations/0010_tvorba_post.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								soustredeni/migrations/0010_tvorba_post.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 21:35 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('soustredeni', '0009_tvorba_relink5'), | ||||||
|  |         ('tvorba', '0003_tvorba_post'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |     ] | ||||||
|  | @ -10,7 +10,7 @@ from django.conf import settings | ||||||
| from personalni.models import Resitel, Organizator | from personalni.models import Resitel, Organizator | ||||||
| 
 | 
 | ||||||
| from seminar.models.base import SeminarModelBase | from seminar.models.base import SeminarModelBase | ||||||
| import seminar.models as am # tvorba | import tvorba.models as am | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								split-apps-meta/polymorphic
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								split-apps-meta/polymorphic
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | django-polymorphic by *nemělo* být potřeba řešit, protože se odkazuje na id contenttype a tedy když přepisujeme ctype na správném místě rovnou, tak to bude fungovat. IN THEORY. | ||||||
|  | 
 | ||||||
|  | Better safe than sorry: přidáme si v seminar.pre vazbu na model contenttypes. (technicky asi měl být všude?) | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| from django.contrib import admin | from django.contrib import admin | ||||||
| from django.forms import ModelForm | from django.forms import ModelForm | ||||||
| from django.core.exceptions import ValidationError | from django.core.exceptions import ValidationError | ||||||
|  | from django.urls import reverse | ||||||
| 
 | 
 | ||||||
| from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter | from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter | ||||||
| from django.utils.safestring import mark_safe | from django.utils.safestring import mark_safe | ||||||
|  | @ -9,7 +10,7 @@ from django.utils.safestring import mark_safe | ||||||
| 
 | 
 | ||||||
| import soustredeni.models | import soustredeni.models | ||||||
| 
 | 
 | ||||||
| from seminar.models import Rocnik, ZmrazenaVysledkovka, Deadline, Uloha, Problem, Tema, Clanek, Cislo # tvorba | from tvorba.models import Rocnik, ZmrazenaVysledkovka, Deadline, Uloha, Problem, Tema, Clanek, Cislo # tvorba | ||||||
| 
 | 
 | ||||||
| admin.site.register(Rocnik) | admin.site.register(Rocnik) | ||||||
| admin.site.register(ZmrazenaVysledkovka) | admin.site.register(ZmrazenaVysledkovka) | ||||||
|  | @ -59,9 +60,6 @@ class CisloForm(ModelForm): | ||||||
| 		# 			if problem not in \ | 		# 			if problem not in \ | ||||||
| 		# 				(Problem.STAV_ZADANY, Problem.STAV_VYRESENY): | 		# 				(Problem.STAV_ZADANY, Problem.STAV_VYRESENY): | ||||||
| 		# 				errors.append(ValidationError('Problém %s není zadaný ani vyřešený', code=problem)) | 		# 				errors.append(ValidationError('Problém %s není zadaný ani vyřešený', code=problem)) | ||||||
| 		# if errors: |  | ||||||
| 		# 	errors.append(ValidationError(mark_safe('<b>Pokud chceš učinit všechny problémy, co nejsou zadané ani vyřešené, zadanými a číslo zveřejnit, můžeš to udělat pomocí akce v <a href="/admin/seminar/cislo">seznamu čísel</a></b>'))) |  | ||||||
| 		# 	raise ValidationError(errors) |  | ||||||
| 
 | 
 | ||||||
| 		errors = [] | 		errors = [] | ||||||
| 		for ch in Uloha.objects.filter(cislo_zadani=self.instance): | 		for ch in Uloha.objects.filter(cislo_zadani=self.instance): | ||||||
|  | @ -70,7 +68,7 @@ class CisloForm(ModelForm): | ||||||
| 					ValidationError('Úloha %(uloha)s není zadaná ani vyřešená', params={'uloha': ch})) | 					ValidationError('Úloha %(uloha)s není zadaná ani vyřešená', params={'uloha': ch})) | ||||||
| 		if errors: | 		if errors: | ||||||
| 			errors.append(ValidationError(mark_safe( | 			errors.append(ValidationError(mark_safe( | ||||||
| 				'<b>Pokud chceš učinit všechny problémy, co nejsou zadané ani vyřešené, zadanými a číslo zveřejnit, můžeš to udělat pomocí akce v <a href="/admin/seminar/cislo">seznamu čísel</a></b>'))) | 				'<b>Pokud chceš učinit všechny problémy, co nejsou zadané ani vyřešené, zadanými a číslo zveřejnit, můžeš to udělat pomocí akce v <a href="' +  reverse('admin:tvorba_cislo_changelist') + '">seznamu čísel</a></b>'))) | ||||||
| 		if self.cleaned_data.get('datum_vydani') == None: | 		if self.cleaned_data.get('datum_vydani') == None: | ||||||
| 			self.add_error('datum_vydani','Číslo určené ke zveřejnění nemá nastavené datum vydání') | 			self.add_error('datum_vydani','Číslo určené ke zveřejnění nemá nastavené datum vydání') | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										197
									
								
								tvorba/migrations/0001_tvorba_create.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								tvorba/migrations/0001_tvorba_create.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,197 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 11:37 | ||||||
|  | 
 | ||||||
|  | import datetime | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  | import django.utils.timezone | ||||||
|  | import seminar.models.tvorba | ||||||
|  | import tvorba.models | ||||||
|  | import taggit.managers | ||||||
|  | 
 | ||||||
|  | def nastav_nove_contenttypes(apps, schema_editor): | ||||||
|  |     ContentType = apps.get_model('contenttypes', 'ContentType') | ||||||
|  |     for m in ('zmrazenavysledkovka', 'deadline', 'cislo', 'rocnik', 'pohadka', 'tema', 'problem', 'problemy_opravovatele', 'uloha', 'clanek'): | ||||||
|  |         ContentType.objects.filter(app_label='seminar', model=m).update(app_label='tvorba') | ||||||
|  | 
 | ||||||
|  | def nastav_stare_contenttypes(apps, schema_editor): | ||||||
|  |     ContentType = apps.get_model('contenttypes', 'ContentType') | ||||||
|  |     for m in ('zmrazenavysledkovka', 'deadline', 'cislo', 'rocnik', 'pohadka', 'tema', 'problem', 'problemy_opravovatele', 'uloha', 'clanek'): | ||||||
|  |         ContentType.objects.filter(app_label='tvorba', model=m).update(app_label='seminar') | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     initial = True | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('seminar', '0137_tvorba_unmanage'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Cislo', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(primary_key=True, serialize=False)), | ||||||
|  |                 ('poradi', models.CharField(db_index=True, help_text='Většinou jen "1", vyjímečně "7-8", lexikograficky určuje pořadí v ročníku!', max_length=32, verbose_name='název čísla')), | ||||||
|  |                 ('datum_vydani', models.DateField(blank=True, help_text='Datum vydání finální verze', null=True, verbose_name='datum vydání')), | ||||||
|  |                 ('verejne_db', models.BooleanField(db_column='verejne', default=False, verbose_name='číslo zveřejněno')), | ||||||
|  |                 ('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k číslu (plain text)', verbose_name='neveřejná poznámka')), | ||||||
|  |                 ('pdf', models.FileField(blank=True, help_text='PDF čísla, které si mohou řešitelé stáhnout', null=True, storage=seminar.models.tvorba.OverwriteStorage(), upload_to=tvorba.models.cislo_pdf_filename, verbose_name='pdf')), | ||||||
|  |                 ('titulka_nahled', models.ImageField(blank=True, help_text='Obrázek titulní strany, generuje se automaticky', null=True, upload_to=tvorba.models.cislo_png_filename, verbose_name='Obrázek titulní strany')), | ||||||
|  |                 ('rocnik', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='cisla', to='tvorba.rocnik', verbose_name='ročník')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Číslo', | ||||||
|  |                 'verbose_name_plural': 'Čísla', | ||||||
|  |                 'db_table': 'seminar_cisla', | ||||||
|  |                 'ordering': ['-rocnik__rocnik', '-poradi'], | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Deadline', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(primary_key=True, serialize=False)), | ||||||
|  |                 ('deadline', models.DateTimeField(default=datetime.datetime(2024, 10, 30, 22, 59, 59, 999999, tzinfo=datetime.timezone.utc))), | ||||||
|  |                 ('cislo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='deadline_v_cisle', to='tvorba.cislo', verbose_name='deadline v čísle')), | ||||||
|  |                 ('typ', models.CharField(choices=[('cisla', 'Deadline celého čísla'), ('prvni', 'První deadline'), ('prvniasous', 'Sousový a první deadline'), ('sous', 'Sousový deadline')], max_length=32, verbose_name='typ deadlinu')), | ||||||
|  |                 ('verejna_vysledkovka', models.BooleanField(db_column='verejna_vysledkovka', default=False, verbose_name='veřejná výsledkovka')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Deadline', | ||||||
|  |                 'verbose_name_plural': 'Deadliny', | ||||||
|  |                 'db_table': 'seminar_deadliny', | ||||||
|  |                 'ordering': ['deadline'], | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Pohadka', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(primary_key=True, serialize=False)), | ||||||
|  |                 ('vytvoreno', models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False, verbose_name='Vytvořeno')), | ||||||
|  |                 ('autor', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='personalni.organizator', verbose_name='Autor pohádky')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Pohádka', | ||||||
|  |                 'verbose_name_plural': 'Pohádky', | ||||||
|  |                 'db_table': 'seminar_pohadky', | ||||||
|  |                 'ordering': ['vytvoreno'], | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Problem', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(primary_key=True, serialize=False)), | ||||||
|  |                 ('nazev', models.CharField(max_length=256, verbose_name='název')), | ||||||
|  |                 ('stav', models.CharField(choices=[('navrh', 'Návrh'), ('zadany', 'Zadaný'), ('vyreseny', 'Vyřešený'), ('smazany', 'Smazaný')], default='navrh', max_length=32, verbose_name='stav problému')), | ||||||
|  |                 ('autor', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='autor_problemu_%(class)s', to='personalni.organizator', verbose_name='autor problému')), | ||||||
|  |                 ('garant', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='garant_problemu_%(class)s', to='personalni.organizator', verbose_name='garant zadaného problému')), | ||||||
|  |                 ('nadproblem', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='podproblem', to='tvorba.problem', verbose_name='nadřazený problém')), | ||||||
|  |                 ('opravovatele', models.ManyToManyField(blank=True, related_name='opravovatele_%(class)s', through='tvorba.Problemy_Opravovatele', to='personalni.organizator', verbose_name='opravovatelé')), | ||||||
|  |                 ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), | ||||||
|  |                 ('zamereni', taggit.managers.TaggableManager(blank=True, help_text='Zaměření M/F/I/O problému, příp. další tagy', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='zaměření')), | ||||||
|  |                 ('poznamka', models.TextField(blank=True, help_text='Neveřejný návrh úlohy, návrh řešení, text zadání, poznámky ...', verbose_name='org poznámky (HTML)')), | ||||||
|  |                 ('kod', models.CharField(blank=True, default='', help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku', max_length=32, verbose_name='lokální kód')), | ||||||
|  |                 ('vytvoreno', models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False, verbose_name='vytvořeno')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Problém', | ||||||
|  |                 'verbose_name_plural': 'Problémy', | ||||||
|  |                 'db_table': 'seminar_problemy', | ||||||
|  |                 'ordering': ['nazev'], | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Problemy_Opravovatele', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(primary_key=True, serialize=False)), | ||||||
|  |                 ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tvorba.problem')), | ||||||
|  |                 ('organizator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='personalni.organizator')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'db_table': 'seminar_problemy_opravovatele', | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Rocnik', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(primary_key=True, serialize=False)), | ||||||
|  |                 ('prvni_rok', models.IntegerField(db_index=True, unique=True, verbose_name='první rok')), | ||||||
|  |                 ('rocnik', models.IntegerField(db_index=True, unique=True, verbose_name='číslo ročníku')), | ||||||
|  |                 ('exportovat', models.BooleanField(db_column='exportovat', default=False, help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti), a to jen čísla s veřejnou výsledkovkou', verbose_name='export do AESOPa')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Ročník', | ||||||
|  |                 'verbose_name_plural': 'Ročníky', | ||||||
|  |                 'db_table': 'seminar_rocniky', | ||||||
|  |                 'ordering': ['-rocnik'], | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Clanek', | ||||||
|  |             fields=[ | ||||||
|  |                 ('problem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tvorba.problem')), | ||||||
|  |                 ('strana', models.PositiveIntegerField(blank=True, null=True, verbose_name='první strana')), | ||||||
|  |                 ('cislo', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vydane_clanky', to='tvorba.cislo', verbose_name='číslo vydání')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Článek', | ||||||
|  |                 'verbose_name_plural': 'Články', | ||||||
|  |                 'db_table': 'seminar_clanky', | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |             bases=('tvorba.problem',), | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Tema', | ||||||
|  |             fields=[ | ||||||
|  |                 ('problem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tvorba.problem')), | ||||||
|  |                 ('tema_typ', models.CharField(choices=[('tema', 'Téma'), ('serial', 'Seriál')], default='tema', max_length=16, verbose_name='Typ tématu')), | ||||||
|  |                 ('rocnik', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='temata', to='tvorba.rocnik', verbose_name='ročník')), | ||||||
|  |                 ('abstrakt', models.TextField(blank=True, verbose_name='Abstrakt na rozcestník')), | ||||||
|  |                 ('obrazek', models.ImageField(blank=True, null=True, upload_to='', verbose_name='Obrázek na rozcestník')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Téma', | ||||||
|  |                 'verbose_name_plural': 'Témata', | ||||||
|  |                 'db_table': 'seminar_temata', | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |             bases=('tvorba.problem',), | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Uloha', | ||||||
|  |             fields=[ | ||||||
|  |                 ('problem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tvorba.problem')), | ||||||
|  |                 ('max_body', models.DecimalField(blank=True, decimal_places=1, max_digits=8, null=True, verbose_name='maximum bodů')), | ||||||
|  |                 ('cislo_zadani', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='zadane_ulohy', to='tvorba.cislo', verbose_name='číslo zadání')), | ||||||
|  |                 ('cislo_reseni', models.ForeignKey(blank=True, help_text='Číslo s řešením úlohy, jen pro úlohy', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='resene_ulohy', to='tvorba.cislo', verbose_name='číslo řešení')), | ||||||
|  |                 ('cislo_deadline', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='deadlinove_ulohy', to='tvorba.cislo', verbose_name='číslo deadlinu')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Úloha', | ||||||
|  |                 'verbose_name_plural': 'Úlohy', | ||||||
|  |                 'db_table': 'seminar_ulohy', | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |             bases=('tvorba.problem',), | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='ZmrazenaVysledkovka', | ||||||
|  |             fields=[ | ||||||
|  |                 ('deadline', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='vysledkovka_v_deadlinu', serialize=False, to='tvorba.deadline')), | ||||||
|  |                 ('html', models.TextField()), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Zmražená výsledkovka', | ||||||
|  |                 'verbose_name_plural': 'Zmražené výsledkovky', | ||||||
|  |                 'db_table': 'seminar_vysledkovky', | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.RunPython(nastav_nove_contenttypes, nastav_stare_contenttypes), | ||||||
|  |     ] | ||||||
							
								
								
									
										54
									
								
								tvorba/migrations/0002_tvorba_manage.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								tvorba/migrations/0002_tvorba_manage.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,54 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 21:29 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('tvorba', '0001_tvorba_create'), | ||||||
|  |         ('seminar', '0138_tvorba_delete'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='cislo', | ||||||
|  |             options={'ordering': ['-rocnik__rocnik', '-poradi'], 'verbose_name': 'Číslo', 'verbose_name_plural': 'Čísla'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='clanek', | ||||||
|  |             options={'verbose_name': 'Článek', 'verbose_name_plural': 'Články'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='deadline', | ||||||
|  |             options={'ordering': ['deadline'], 'verbose_name': 'Deadline', 'verbose_name_plural': 'Deadliny'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='pohadka', | ||||||
|  |             options={'ordering': ['vytvoreno'], 'verbose_name': 'Pohádka', 'verbose_name_plural': 'Pohádky'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='problem', | ||||||
|  |             options={'ordering': ['nazev'], 'verbose_name': 'Problém', 'verbose_name_plural': 'Problémy'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='problemy_opravovatele', | ||||||
|  |             options={}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='rocnik', | ||||||
|  |             options={'ordering': ['-rocnik'], 'verbose_name': 'Ročník', 'verbose_name_plural': 'Ročníky'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='tema', | ||||||
|  |             options={'verbose_name': 'Téma', 'verbose_name_plural': 'Témata'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='uloha', | ||||||
|  |             options={'verbose_name': 'Úloha', 'verbose_name_plural': 'Úlohy'}, | ||||||
|  |         ), | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='zmrazenavysledkovka', | ||||||
|  |             options={'verbose_name': 'Zmražená výsledkovka', 'verbose_name_plural': 'Zmražené výsledkovky'}, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										13
									
								
								tvorba/migrations/0003_tvorba_post.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								tvorba/migrations/0003_tvorba_post.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 21:34 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('tvorba', '0002_tvorba_manage'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |     ] | ||||||
							
								
								
									
										708
									
								
								tvorba/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										708
									
								
								tvorba/models.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,708 @@ | ||||||
|  | import datetime | ||||||
|  | import os | ||||||
|  | import subprocess | ||||||
|  | import pathlib | ||||||
|  | import tempfile | ||||||
|  | import logging | ||||||
|  | 
 | ||||||
|  | from django.contrib.sites.shortcuts import get_current_site | ||||||
|  | from django.db import models | ||||||
|  | from django.db.models import Q | ||||||
|  | from django.template.loader import render_to_string | ||||||
|  | from django.utils import timezone | ||||||
|  | from django.conf import settings | ||||||
|  | from django.urls import reverse | ||||||
|  | from django.core.cache import cache | ||||||
|  | from django.core.exceptions import ObjectDoesNotExist, ValidationError | ||||||
|  | from django.utils.text import get_valid_filename | ||||||
|  | from django.utils.functional import cached_property | ||||||
|  | 
 | ||||||
|  | from solo.models import SingletonModel | ||||||
|  | from taggit.managers import TaggableManager | ||||||
|  | 
 | ||||||
|  | from reversion import revisions as reversion | ||||||
|  | 
 | ||||||
|  | from tvorba.utils import roman, aktivniResitele | ||||||
|  | from treenode import treelib | ||||||
|  | 
 | ||||||
|  | from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované) | ||||||
|  | 
 | ||||||
|  | from polymorphic.models import PolymorphicModel | ||||||
|  | 
 | ||||||
|  | from django.core.mail import EmailMessage | ||||||
|  | 
 | ||||||
|  | from seminar.models.base import SeminarModelBase | ||||||
|  | from seminar.models.tvorba import OverwriteStorage | ||||||
|  | from personalni.models import Prijemce, Organizator | ||||||
|  | 
 | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Rocnik(SeminarModelBase): | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_rocniky' | ||||||
|  | 		verbose_name = 'Ročník' | ||||||
|  | 		verbose_name_plural = 'Ročníky' | ||||||
|  | 		ordering = ['-rocnik'] | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	prvni_rok = models.IntegerField('první rok', db_index=True, unique=True) | ||||||
|  | 
 | ||||||
|  | 	rocnik = models.IntegerField('číslo ročníku', db_index=True, unique=True) | ||||||
|  | 
 | ||||||
|  | 	exportovat = models.BooleanField('export do AESOPa', db_column='exportovat', default=False, | ||||||
|  | 			help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti),' | ||||||
|  | 			' a to jen čísla s veřejnou výsledkovkou') | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return '{} ({}/{})'.format(self.rocnik, self.prvni_rok, self.prvni_rok+1) | ||||||
|  | 
 | ||||||
|  | 	# Ročník v římských číslech | ||||||
|  | 	def roman(self): | ||||||
|  | 		return roman(int(self.rocnik)) | ||||||
|  | 
 | ||||||
|  | 	def verejne(self): | ||||||
|  | 		return len(self.verejna_cisla()) > 0 | ||||||
|  | 	verejne.boolean = True | ||||||
|  | 	verejne.short_description = 'Veřejný (jen dle čísel)' | ||||||
|  | 
 | ||||||
|  | 	def neverejna_cisla(self): | ||||||
|  | 		vc = [c for c in self.cisla.all() if not c.verejne()] | ||||||
|  | 		vc.sort(key=lambda c: c.poradi) | ||||||
|  | 		return vc | ||||||
|  | 
 | ||||||
|  | 	def verejna_cisla(self): | ||||||
|  | 		vc = [c for c in self.cisla.all() if c.verejne()] | ||||||
|  | 		vc.sort(key=lambda c: c.poradi) | ||||||
|  | 		return vc | ||||||
|  | 
 | ||||||
|  | 	def posledni_verejne_cislo(self): | ||||||
|  | 		vc = self.verejna_cisla() | ||||||
|  | 		return vc[-1] if vc else None | ||||||
|  | 
 | ||||||
|  | 	def verejne_vysledkovky_cisla(self): | ||||||
|  | 		vc = list(self.cisla.filter(deadline_v_cisle__verejna_vysledkovka=True).distinct()) | ||||||
|  | 		vc.sort(key=lambda c: c.poradi) | ||||||
|  | 		return vc | ||||||
|  | 
 | ||||||
|  | 	def posledni_zverejnena_vysledkovka_cislo(self): | ||||||
|  | 		vc = self.verejne_vysledkovky_cisla() | ||||||
|  | 		return vc[-1] if vc else None | ||||||
|  | 
 | ||||||
|  | 	def druhy_rok(self): | ||||||
|  | 		return self.prvni_rok + 1 | ||||||
|  | 
 | ||||||
|  | 	def verejne_url(self): | ||||||
|  | 		return reverse('seminar_rocnik', kwargs={'rocnik': self.rocnik}) | ||||||
|  | 
 | ||||||
|  | 	@classmethod | ||||||
|  | 	def cached_rocnik(cls, r_id): | ||||||
|  | 		name = 'rocnik_%s' % (r_id, ) | ||||||
|  | 		c = cache.get(name) | ||||||
|  | 		if c is None: | ||||||
|  | 			c = cls.objects.get(id=r_id) | ||||||
|  | 			cache.set(name, c, 300) | ||||||
|  | 		return c | ||||||
|  | 		 | ||||||
|  | 	def save(self, *args, **kwargs): | ||||||
|  | 		super().save(*args, **kwargs) | ||||||
|  | 		# *Node.save() aktualizuje název *Nodu. | ||||||
|  | 		try: | ||||||
|  | 			self.rocniknode.save() | ||||||
|  | 		except ObjectDoesNotExist: | ||||||
|  | 			# Neexistující *Node nemá smysl aktualizovat. | ||||||
|  | 			pass | ||||||
|  | 
 | ||||||
|  | def cislo_pdf_filename(self, filename): | ||||||
|  | 	rocnik = str(self.rocnik.rocnik) | ||||||
|  | 	return pathlib.Path('cislo', 'pdf', rocnik, '{}-{}.pdf'.format(rocnik, self.poradi)) | ||||||
|  | 
 | ||||||
|  | def cislo_png_filename(self, filename): | ||||||
|  | 	rocnik = str(self.rocnik.rocnik) | ||||||
|  | 	return pathlib.Path('cislo', 'png', rocnik, '{}-{}.png'.format(rocnik, self.poradi)) | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Cislo(SeminarModelBase): | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_cisla' | ||||||
|  | 		verbose_name = 'Číslo' | ||||||
|  | 		verbose_name_plural = 'Čísla' | ||||||
|  | 		ordering = ['-rocnik__rocnik', '-poradi'] | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='cisla', | ||||||
|  | 		db_index=True,on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	poradi = models.CharField('název čísla', max_length=32, db_index=True, | ||||||
|  | 		help_text='Většinou jen "1", vyjímečně "7-8", lexikograficky určuje pořadí v ročníku!') | ||||||
|  | 
 | ||||||
|  | 	datum_vydani = models.DateField('datum vydání', blank=True, null=True, | ||||||
|  | 		help_text='Datum vydání finální verze') | ||||||
|  | 
 | ||||||
|  | 	verejne_db = models.BooleanField('číslo zveřejněno', | ||||||
|  | 		db_column='verejne', default=False) | ||||||
|  | 
 | ||||||
|  | 	poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||||
|  | 		help_text='Neveřejná poznámka k číslu (plain text)') | ||||||
|  | 
 | ||||||
|  | 	pdf = models.FileField('pdf', upload_to=cislo_pdf_filename, null=True, blank=True, | ||||||
|  | 		help_text='PDF čísla, které si mohou řešitelé stáhnout', storage=OverwriteStorage()) | ||||||
|  | 
 | ||||||
|  | 	titulka_nahled = models.ImageField('Obrázek titulní strany', upload_to=cislo_png_filename, null=True, blank=True, | ||||||
|  | 		help_text='Obrázek titulní strany, generuje se automaticky') | ||||||
|  | 
 | ||||||
|  | 	def kod(self): | ||||||
|  | 		return '%s.%s' % (self.rocnik.rocnik, self.poradi) | ||||||
|  | 	kod.short_description = 'Kód čísla' | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		# Potenciální DB HOG, pokud by se ročník necachoval | ||||||
|  | 		r = Rocnik.cached_rocnik(self.rocnik_id) | ||||||
|  | 		return '{}.{}'.format(r.rocnik, self.poradi) | ||||||
|  | 
 | ||||||
|  | 	def verejne(self): | ||||||
|  | 		return self.verejne_db | ||||||
|  | 	verejne.boolean = True | ||||||
|  | 
 | ||||||
|  | 	def verejne_url(self): | ||||||
|  | 		return reverse('seminar_cislo', kwargs={'rocnik': self.rocnik.rocnik, 'cislo': self.poradi}) | ||||||
|  | 
 | ||||||
|  | 	def absolute_url(self): | ||||||
|  | 		return "https://" + str(get_current_site(None)) + self.verejne_url() | ||||||
|  | 
 | ||||||
|  | 	def nasledujici(self): | ||||||
|  | 		"Vrací None, pokud je toto poslední" | ||||||
|  | 		return self.relativni_v_rocniku(1) | ||||||
|  | 
 | ||||||
|  | 	def predchozi(self): | ||||||
|  | 		"Vrací None, pokud je toto první" | ||||||
|  | 		return self.relativni_v_rocniku(-1) | ||||||
|  | 
 | ||||||
|  | 	def relativni_v_rocniku(self, rel_index): | ||||||
|  | 		"Číslo o `index` dále v ročníku. None pokud neexistuje." | ||||||
|  | 		cs = self.rocnik.cisla.order_by('poradi').all() | ||||||
|  | 		i = list(cs).index(self) + rel_index | ||||||
|  | 		if (i < 0) or (i >= len(cs)): | ||||||
|  | 			return None | ||||||
|  | 		return cs[i] | ||||||
|  | 
 | ||||||
|  | 	def vygeneruj_nahled(self): | ||||||
|  | 		VYSKA = 594 | ||||||
|  | 		sirka = int(VYSKA*210/297) | ||||||
|  | 		if not self.pdf: | ||||||
|  | 			return | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 		# Pokud obrázek neexistuje nebo není aktuální, vytvoř jej | ||||||
|  | 		if not self.titulka_nahled or os.path.getmtime(self.titulka_nahled.path) < os.path.getmtime(self.pdf.path): | ||||||
|  | 			png_filename = pathlib.Path(tempfile.mkdtemp(), 'nahled.png') | ||||||
|  | 
 | ||||||
|  | 			subprocess.run([ | ||||||
|  | 				"gs", | ||||||
|  | 				"-sstdout=%stderr", | ||||||
|  | 				"-dSAFER", | ||||||
|  | 				"-dNOPAUSE", | ||||||
|  | 				"-dBATCH", | ||||||
|  | 				"-dNOPROMPT", | ||||||
|  | 				"-sDEVICE=png16m", | ||||||
|  | 				"-r300x300", | ||||||
|  | 				"-dFirstPage=1d", | ||||||
|  | 				"-dLastPage=1d", | ||||||
|  | 				"-sOutputFile=" + str(png_filename), | ||||||
|  | 				"-f%s" % self.pdf.path | ||||||
|  | 				], | ||||||
|  | 				check=True, | ||||||
|  | 				capture_output=True | ||||||
|  | 			) | ||||||
|  | 
 | ||||||
|  | 			with open(png_filename,'rb') as f: | ||||||
|  | 				self.titulka_nahled.save('',f,True) | ||||||
|  | 
 | ||||||
|  | 			png_filename.unlink() | ||||||
|  | 			png_filename.parent.rmdir() | ||||||
|  | 
 | ||||||
|  | 	@classmethod | ||||||
|  | 	def get(cls, rocnik, cislo): | ||||||
|  | 		try: | ||||||
|  | 			r = Rocnik.objects.get(rocnik=rocnik) | ||||||
|  | 			c = r.cisla.get(poradi=cislo) | ||||||
|  | 		except ObjectDoesNotExist: | ||||||
|  | 			return None | ||||||
|  | 		return c | ||||||
|  | 
 | ||||||
|  | 	def __init__(self, *args, **kwargs): | ||||||
|  | 		super().__init__(*args, **kwargs) | ||||||
|  | 		self.__original_verejne = self.verejne_db | ||||||
|  | 
 | ||||||
|  | 	def posli_cislo_mailem(self): | ||||||
|  | 		# parametry e-mailu | ||||||
|  | 		odkaz = self.absolute_url() | ||||||
|  | 
 | ||||||
|  | 		poslat_z_mailu = 'zadani@mam.mff.cuni.cz' | ||||||
|  | 		predmet = 'Vyšlo číslo {}'.format(self.kod()) | ||||||
|  | 		# TODO Možná nechceme všem psát „Ahoj“, např. příjemcům… | ||||||
|  | 		text_mailu = 'Ahoj,\n' \ | ||||||
|  | 			   'na adrese {} najdete nejnovější číslo.\n' \ | ||||||
|  | 			   'Vaše M&M\n'.format(odkaz) | ||||||
|  | 
 | ||||||
|  | 		predmet_prvni = 'Právě vyšlo 1. číslo M&M, pomoz nám ho poslat dál!' | ||||||
|  | 		text_mailu_prvni = 'Milý řešiteli,\n'\ | ||||||
|  | 			'právě jsme na našem webu zveřejnili první číslo {}. ročníku, najdeš ho na tomto odkazu: {}.\n\n'\ | ||||||
|  | 			'Doufáme, že tě M&M baví, a byli bychom rádi, kdyby mohlo dělat radost i dalším středoškolákům. Máme na tebe proto jednu prosbu. Sdílej prosím odkaz alespoň s jedním svým kamarádem, který by mohl mít o řešení M&M zájem. Je to pro nás moc důležité a velmi nám tím pomůžeš. Díky!\n\n'\ | ||||||
|  | 			'Organizátoři M&M\n'.format(self.rocnik.rocnik, odkaz) | ||||||
|  | 
 | ||||||
|  | 		predmet_resitel = predmet_prvni if self.poradi == "1" else predmet | ||||||
|  | 		text_mailu_resitel = text_mailu_prvni if self.poradi == "1" else text_mailu | ||||||
|  | 
 | ||||||
|  | 		# Prijemci e-mailu | ||||||
|  | 		resitele_vsichni = aktivniResitele(self).filter(zasilat_cislo_emailem=True) | ||||||
|  | 
 | ||||||
|  | 		def posli(subject, text, resitele): | ||||||
|  | 			emaily = map(lambda resitel: resitel.osoba.email, resitele) | ||||||
|  | 
 | ||||||
|  | 			email = EmailMessage( | ||||||
|  | 				subject=subject, | ||||||
|  | 				body=text, | ||||||
|  | 				from_email=poslat_z_mailu, | ||||||
|  | 				bcc=list(emaily) | ||||||
|  | 				#bcc = příjemci skryté kopie | ||||||
|  | 			) | ||||||
|  | 
 | ||||||
|  | 			email.send() | ||||||
|  | 
 | ||||||
|  | 		paticka = "---\nK odběru těchto e-mailů jste se přihlásili na stránkách https://mam.matfyz.cz. Z odběru se lze odhlásit na https://mam.matfyz.cz/resitel/osobni-udaje/" | ||||||
|  | 
 | ||||||
|  | 		posli(predmet_resitel, text_mailu_resitel + paticka, resitele_vsichni.filter(zasilat_cislo_papirove=False)) | ||||||
|  | 		posli(predmet_resitel, text_mailu_resitel + 'P. S. Brzy budeme též rozesílat papírovou verzi čísla. Připomínáme, že pokud papírovou verzi čísla nevyužijete, můžete v https://mam.mff.cuni.cz/resitel/osobni-udaje/ zaškrtnout, abychom vám ji neposílali. Čísla vždy můžete nalézt v našem archivu a dál vám budou chodit e-mailem. Děkujeme.\n' + paticka, | ||||||
|  | 			  resitele_vsichni.filter(zasilat_cislo_papirove=True)) | ||||||
|  | 
 | ||||||
|  | 		paticka_prijemce = "---\nPokud tyto e-maily nechcete nadále dostávat, prosíme, ozvěte se nám na mam@matfyz.cz." | ||||||
|  | 		posli(predmet, text_mailu + paticka_prijemce, Prijemce.objects.filter(zasilat_cislo_emailem=True)) | ||||||
|  | 
 | ||||||
|  | 	def save(self, *args, **kwargs): | ||||||
|  | 		super().save(*args, **kwargs) | ||||||
|  | 		self.vygeneruj_nahled() | ||||||
|  | 		# Při zveřejnění pošle mail | ||||||
|  | 		if self.verejne_db and not self.__original_verejne: | ||||||
|  | 			self.posli_cislo_mailem() | ||||||
|  | 		# *Node.save() aktualizuje název *Nodu. | ||||||
|  | 		try: | ||||||
|  | 			self.cislonode.save() | ||||||
|  | 		except ObjectDoesNotExist: | ||||||
|  | 			# Neexistující *Node nemá smysl aktualizovat, ale je potřeba ho naopak vyrobit | ||||||
|  | 			logger.warning(f'Číslo {self} nemělo ČísloNode, vyrábím…') | ||||||
|  | 			from seminar.models.treenode import CisloNode | ||||||
|  | 			CisloNode.objects.create(cislo=self) | ||||||
|  | 
 | ||||||
|  | 	def zlomovy_deadline_pro_papirove_cislo(self): | ||||||
|  | 		prvni_deadline = Deadline.objects.filter(Q(typ=Deadline.TYP_PRVNI) | Q(typ=Deadline.TYP_PRVNI_A_SOUS), cislo=self).first() | ||||||
|  | 		if prvni_deadline is None: | ||||||
|  | 			posledni_deadline = self.posledni_deadline | ||||||
|  | 			if posledni_deadline is None: | ||||||
|  | 				# TODO promyslet, co se má stát tady | ||||||
|  | 				return Deadline.objects.filter(Q(cislo__poradi__lt=self.poradi, cislo__rocnik=self.rocnik) | Q(cislo__rocnik__rocnik__lt=self.rocnik.rocnik)).order_by("deadline").last() | ||||||
|  | 			return posledni_deadline | ||||||
|  | 		return prvni_deadline | ||||||
|  | 
 | ||||||
|  | 	@property | ||||||
|  | 	def posledni_deadline(self): | ||||||
|  | 		return self.deadline_v_cisle.all().order_by("deadline").last() | ||||||
|  | 
 | ||||||
|  | class Deadline(SeminarModelBase): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_deadliny' | ||||||
|  | 		verbose_name = 'Deadline' | ||||||
|  | 		verbose_name_plural = 'Deadliny' | ||||||
|  | 		ordering = ['deadline'] | ||||||
|  | 
 | ||||||
|  | 	def __init__(self, *args, **kwargs): | ||||||
|  | 		super().__init__(*args, **kwargs) | ||||||
|  | 		self.__original_verejna_vysledkovka = self.verejna_vysledkovka | ||||||
|  | 
 | ||||||
|  | 	id = models.AutoField(primary_key=True) | ||||||
|  | 
 | ||||||
|  | 	# V ročníku < 26 nastaveno na datetime.datetime.combine(datetime.date(1994 + cislo.rocnik.rocnik, 6, int(cislo.poradi[0])), datetime.time.min) | ||||||
|  | 	deadline = models.DateTimeField(blank=False, default=timezone.make_aware(datetime.datetime.combine(timezone.now(), datetime.time.max))) | ||||||
|  | 
 | ||||||
|  | 	cislo = models.ForeignKey(Cislo, verbose_name='deadline v čísle', | ||||||
|  | 		related_name='deadline_v_cisle', blank=False, | ||||||
|  | 		on_delete=models.CASCADE) | ||||||
|  | 
 | ||||||
|  | 	TYP_CISLA = 'cisla' | ||||||
|  | 	TYP_PRVNI_A_SOUS = 'prvniasous' | ||||||
|  | 	TYP_PRVNI = 'prvni' | ||||||
|  | 	TYP_SOUS = 'sous' | ||||||
|  | 	TYP_CHOICES = [ | ||||||
|  | 		(TYP_CISLA, 'Deadline celého čísla'), | ||||||
|  | 		(TYP_PRVNI, 'První deadline'), | ||||||
|  | 		(TYP_PRVNI_A_SOUS, 'Sousový a první deadline'), | ||||||
|  | 		(TYP_SOUS, 'Sousový deadline'), | ||||||
|  | 	] | ||||||
|  | 	CHOICES_MAP = dict(TYP_CHOICES) | ||||||
|  | 	typ = models.CharField('typ deadlinu', max_length=32, | ||||||
|  | 							choices=TYP_CHOICES, blank=False) | ||||||
|  | 
 | ||||||
|  | 	verejna_vysledkovka = models.BooleanField('veřejná výsledkovka', | ||||||
|  | 											  db_column='verejna_vysledkovka', | ||||||
|  | 											  default=False) | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return self.CHOICES_MAP[self.typ] + " " + str(self.cislo) | ||||||
|  | 
 | ||||||
|  | 	def save(self, *args, **kwargs): | ||||||
|  | 		super().save(*args, **kwargs) | ||||||
|  | 		if self.verejna_vysledkovka and not self.__original_verejna_vysledkovka: | ||||||
|  | 			self.vygeneruj_vysledkovku() | ||||||
|  | 		if not self.verejna_vysledkovka and hasattr(self, "vysledkovka_v_deadlinu"): | ||||||
|  | 			self.vysledkovka_v_deadlinu.delete() | ||||||
|  | 
 | ||||||
|  | 	def vygeneruj_vysledkovku(self): | ||||||
|  | 		from vysledkovky.utils import VysledkovkaCisla | ||||||
|  | 		if hasattr(self, "vysledkovka_v_deadlinu"): | ||||||
|  | 			self.vysledkovka_v_deadlinu.delete() | ||||||
|  | 		vysledkovka = VysledkovkaCisla(self.cislo, jen_verejne=True, do_deadlinu=self) | ||||||
|  | 		if len(vysledkovka.radky_vysledkovky) != 0: | ||||||
|  | 			ZmrazenaVysledkovka.objects.create( | ||||||
|  | 				deadline=self, | ||||||
|  | 				html=render_to_string( | ||||||
|  | 					"vysledkovky/vysledkovka_cisla.html", | ||||||
|  | 					context={"vysledkovka": vysledkovka, "oznaceni_vysledkovky": self.id} | ||||||
|  | 				) | ||||||
|  | 			) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ZmrazenaVysledkovka(SeminarModelBase): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_vysledkovky' | ||||||
|  | 		verbose_name = 'Zmražená výsledkovka' | ||||||
|  | 		verbose_name_plural = 'Zmražené výsledkovky' | ||||||
|  | 
 | ||||||
|  | 	deadline = models.OneToOneField( | ||||||
|  | 		Deadline, | ||||||
|  | 		on_delete=models.CASCADE, | ||||||
|  | 		primary_key=True, | ||||||
|  | 		related_name="vysledkovka_v_deadlinu" | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	html = models.TextField(null=False, blank=False) | ||||||
|  | 
 | ||||||
|  | class Problemy_Opravovatele(SeminarModelBase): | ||||||
|  | 	"""Jen vazebná tabulka pro opravovatele. | ||||||
|  | 
 | ||||||
|  | 	Ona stejně existovala, při přesunu mezi aplikacemi jen potřebujeme zajistit nepřejmenování DB tabulky. | ||||||
|  | 	Proto taky nepotřebuje žádná specifika, ze :py:class:SeminarModelBase: dědí ze zvyku než že by to k něčemu kdy měo být. | ||||||
|  | 	""" | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_problemy_opravovatele' | ||||||
|  | 	 | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	problem = models.ForeignKey('Problem', on_delete=models.CASCADE) | ||||||
|  | 	organizator = models.ForeignKey(Organizator, on_delete=models.CASCADE) | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | # Pozor na následující řádek. *Nekrmit, asi kouše!* | ||||||
|  | class Problem(SeminarModelBase,PolymorphicModel): | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		# Není abstraktní, protože se na něj jinak nedají dělat ForeignKeys. | ||||||
|  | 		# TODO: Udělat to polymorfní (pomocí django-polymorphic), abychom dostali  | ||||||
|  | 		# po těch vazbách přímo tu úlohu/témátko vč. fieldů, které nejsou součástí  | ||||||
|  | 		# modelu Problem? | ||||||
|  | 
 | ||||||
|  | 		#abstract = True | ||||||
|  | 		db_table = 'seminar_problemy' | ||||||
|  | 		verbose_name = 'Problém' | ||||||
|  | 		verbose_name_plural = 'Problémy' | ||||||
|  | 		ordering = ['nazev'] | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	# Název | ||||||
|  | 	nazev = models.CharField('název', max_length=256) # Zveřejnitelný na stránky | ||||||
|  | 
 | ||||||
|  | 	# Problém má podproblémy | ||||||
|  | 	nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém', | ||||||
|  | 		related_name='podproblem', null=True, blank=True, | ||||||
|  | 		on_delete=models.SET_NULL) | ||||||
|  | 
 | ||||||
|  | 	STAV_NAVRH = 'navrh' | ||||||
|  | 	STAV_ZADANY = 'zadany' | ||||||
|  | 	STAV_VYRESENY = 'vyreseny' | ||||||
|  | 	STAV_SMAZANY = 'smazany' | ||||||
|  | 	STAV_CHOICES = [ | ||||||
|  | 		(STAV_NAVRH, 'Návrh'), | ||||||
|  | 		(STAV_ZADANY, 'Zadaný'), | ||||||
|  | 		(STAV_VYRESENY, 'Vyřešený'), | ||||||
|  | 		(STAV_SMAZANY, 'Smazaný'), | ||||||
|  | 		] | ||||||
|  | 	stav = models.CharField('stav problému', max_length=32, choices=STAV_CHOICES, blank=False, default=STAV_NAVRH) | ||||||
|  | 	# Téma je taky Problém, takže má stavy, "zadané" témátko je aktuálně otevřené a dá se k němu něco poslat (řešení nebo článek) | ||||||
|  | 
 | ||||||
|  | 	zamereni = TaggableManager(verbose_name='zaměření',  | ||||||
|  | 		help_text='Zaměření M/F/I/O problému, příp. další tagy', blank=True) | ||||||
|  | 
 | ||||||
|  | 	poznamka = models.TextField('org poznámky (HTML)', blank=True, | ||||||
|  | 		help_text='Neveřejný návrh úlohy, návrh řešení, text zadání, poznámky ...') | ||||||
|  | 
 | ||||||
|  | 	autor = models.ForeignKey(Organizator, verbose_name='autor problému', | ||||||
|  | 		related_name='autor_problemu_%(class)s', null=True, blank=True, | ||||||
|  | 		on_delete=models.SET_NULL) | ||||||
|  | 
 | ||||||
|  | 	garant = models.ForeignKey(Organizator, verbose_name='garant zadaného problému', | ||||||
|  | 		related_name='garant_problemu_%(class)s', null=True, blank=True, | ||||||
|  | 		on_delete=models.SET_NULL) | ||||||
|  | 
 | ||||||
|  | 	opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé', | ||||||
|  | 		blank=True, related_name='opravovatele_%(class)s', through=Problemy_Opravovatele) | ||||||
|  | 
 | ||||||
|  | 	kod = models.CharField('lokální kód', max_length=32, blank=True, default='', | ||||||
|  | 		help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku') | ||||||
|  | 
 | ||||||
|  | 	vytvoreno = models.DateTimeField('vytvořeno', default=timezone.now, blank=True, editable=False) | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return self.nazev | ||||||
|  | 
 | ||||||
|  | 	# Implicitini implementace, jednotlivé dědící třídy si přepíšou | ||||||
|  | 	@cached_property | ||||||
|  | 	def kod_v_rocniku(self): | ||||||
|  | 		if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY: | ||||||
|  | 			if self.nadproblem: | ||||||
|  | 				return self.nadproblem.kod_v_rocniku+".{}".format(self.kod) | ||||||
|  | 			return str(self.kod) | ||||||
|  | 		logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.") | ||||||
|  | 		return f'<Není zadaný: {self.kod}>' | ||||||
|  | 
 | ||||||
|  | #	def verejne(self): | ||||||
|  | #		# aktuálně podle stavu problému | ||||||
|  | #		# FIXME pro některé problémy možná chceme override | ||||||
|  | #		# FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je.  | ||||||
|  | #		# Je to tak správně? Podle aktuální představy ano. | ||||||
|  | #		stav_verejny = False | ||||||
|  | #		if self.stav == 'zadany' or self.stav == 'vyreseny': | ||||||
|  | #			stav_verejny = True | ||||||
|  | #			print("stav_verejny: {}".format(stav_verejny))		 | ||||||
|  | # | ||||||
|  | #		cislo_verejne = False | ||||||
|  | #		cislonode = self.cislo_node() | ||||||
|  | #		if cislonode is None: | ||||||
|  | #			# problém nemá vlastní node, veřejnost posuzujeme jen podle stavu | ||||||
|  | #			print("empty node")		 | ||||||
|  | #			return stav_verejny | ||||||
|  | #		else:	 | ||||||
|  | #			cislo_zadani = cislonode.cislo | ||||||
|  | #			if (cislo_zadani and cislo_zadani.verejne()): | ||||||
|  | #				print("cislo: {}".format(cislo_zadani)) | ||||||
|  | #				cislo_verejne = True | ||||||
|  | #			print("stav_verejny: {}".format(stav_verejny))		 | ||||||
|  | #			print("cislo_verejne: {}".format(cislo_verejne))		 | ||||||
|  | #			return (stav_verejny and cislo_verejne) | ||||||
|  | #	verejne.boolean = True | ||||||
|  | 
 | ||||||
|  | 	def verejne_url(self): | ||||||
|  | 		return reverse('seminar_problem', kwargs={'pk': self.id}) | ||||||
|  | 
 | ||||||
|  | 	def admin_url(self): | ||||||
|  | 			return reverse('admin:tvorba_problem_change', args=(self.id, )) | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def hlavni_problem(self): | ||||||
|  | 		""" Pro daný problém vrátí jeho nejvyšší nadproblém.""" | ||||||
|  | 		problem = self | ||||||
|  | 		while not (problem.nadproblem is None): | ||||||
|  | 			problem = problem.nadproblem | ||||||
|  | 		return problem | ||||||
|  | 
 | ||||||
|  | # FIXME - k úloze | ||||||
|  | 	def body_v_zavorce(self): | ||||||
|  | 		"""Vrať string s body v závorce jsou-li u problému vyplněné, jinak '' | ||||||
|  | 
 | ||||||
|  | 		Je-li desetinná část nulová, nezobrazuj ji. | ||||||
|  | 		""" | ||||||
|  | 		pocet_bodu = None | ||||||
|  | 		if self.body: | ||||||
|  | 			b = self.body | ||||||
|  | 			pocet_bodu = int(b) if int(b) == b else b | ||||||
|  | 		return "({}\u2009b)".format(pocet_bodu) if self.body else "" | ||||||
|  | 
 | ||||||
|  | class Tema(Problem): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_temata' | ||||||
|  | 		verbose_name = 'Téma' | ||||||
|  | 		verbose_name_plural = 'Témata' | ||||||
|  | 	 | ||||||
|  | 	TEMA_TEMA = 'tema' | ||||||
|  | 	TEMA_SERIAL = 'serial' | ||||||
|  | 	TEMA_CHOICES = [ | ||||||
|  | 		(TEMA_TEMA, 'Téma'), | ||||||
|  | 		(TEMA_SERIAL, 'Seriál'), | ||||||
|  | 		] | ||||||
|  | 	tema_typ = models.CharField('Typ tématu', max_length=16, choices=TEMA_CHOICES,  | ||||||
|  | 		blank=False, default=TEMA_TEMA) | ||||||
|  | 
 | ||||||
|  | 	rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',related_name='temata',blank=True, null=True, | ||||||
|  | 		on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	abstrakt = models.TextField('Abstrakt na rozcestník', blank=True) | ||||||
|  | 	obrazek = models.ImageField('Obrázek na rozcestník', null=True, blank=True) | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def kod_v_rocniku(self): | ||||||
|  | 		if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY: | ||||||
|  | 			if self.nadproblem: | ||||||
|  | 				return self.nadproblem.kod_v_rocniku+".t{}".format(self.kod) | ||||||
|  | 			return 't'+self.kod | ||||||
|  | 		logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.") | ||||||
|  | 		return f'<Není zadaný: {self.kod}>' | ||||||
|  | 
 | ||||||
|  | 	def save(self, *args, **kwargs): | ||||||
|  | 		super().save(*args, **kwargs) | ||||||
|  | 		# *Node.save() aktualizuje název *Nodu. | ||||||
|  | 		for tvcn in self.temavcislenode_set.all(): | ||||||
|  | 			tvcn.save() | ||||||
|  | 
 | ||||||
|  | 	def cislo_node(self): | ||||||
|  | 		tema_node_set = self.temavcislenode_set.all() | ||||||
|  | 		tema_cisla_vyskyt = [] | ||||||
|  | 		from seminar.models.treenode import CisloNode | ||||||
|  | 		for tn in tema_node_set: | ||||||
|  | 			tema_cisla_vyskyt.append( | ||||||
|  | 				treelib.get_upper_node_of_type(tn, CisloNode).cislo) | ||||||
|  | 		tema_cisla_vyskyt.sort(key=lambda x:x.datum_vydani) | ||||||
|  | 		prvni_zadani = tema_cisla_vyskyt[0] | ||||||
|  | 		return prvni_zadani.cislonode | ||||||
|  | 
 | ||||||
|  | class Clanek(Problem): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_clanky' | ||||||
|  | 		verbose_name = 'Článek' | ||||||
|  | 		verbose_name_plural = 'Články' | ||||||
|  | 	 | ||||||
|  | 	cislo = models.ForeignKey(Cislo, blank=True, null=True, on_delete=models.PROTECT, | ||||||
|  | 		verbose_name='číslo vydání', related_name='vydane_clanky') | ||||||
|  | 	 | ||||||
|  | 	strana = models.PositiveIntegerField(verbose_name="první strana", blank=True, null=True) | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def kod_v_rocniku(self): | ||||||
|  | 		if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY: | ||||||
|  | # Nemělo by být potřeba | ||||||
|  | #			if self.nadproblem: | ||||||
|  | #				return self.nadproblem.kod_v_rocniku+".c{}".format(self.kod) | ||||||
|  | 			return "c" + self.kod | ||||||
|  | 		logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.") | ||||||
|  | 		return f'<Není zadaný: {self.kod}>' | ||||||
|  | 	 | ||||||
|  | 	def node(self): | ||||||
|  | 		return None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Uloha(Problem): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_ulohy' | ||||||
|  | 		verbose_name = 'Úloha' | ||||||
|  | 		verbose_name_plural = 'Úlohy' | ||||||
|  | 	 | ||||||
|  | 	cislo_zadani = models.ForeignKey(Cislo, verbose_name='číslo zadání', blank=True,  | ||||||
|  | 		null=True, related_name='zadane_ulohy', on_delete=models.PROTECT) | ||||||
|  | 	 | ||||||
|  | 	cislo_deadline = models.ForeignKey(Cislo, verbose_name='číslo deadlinu', blank=True,  | ||||||
|  | 		null=True, related_name='deadlinove_ulohy', on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	cislo_reseni = models.ForeignKey(Cislo, verbose_name='číslo řešení', blank=True,  | ||||||
|  | 		null=True, related_name='resene_ulohy', | ||||||
|  | 		help_text='Číslo s řešením úlohy, jen pro úlohy', | ||||||
|  | 		on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	max_body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='maximum bodů',  | ||||||
|  | 		blank=True, null=True) | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def kod_v_rocniku(self): | ||||||
|  | 		if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY: | ||||||
|  | 			return f"{self.cislo_zadani.poradi}.{self.kod}" | ||||||
|  | 		logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.") | ||||||
|  | 		return f'<Není zadaný: {self.kod}>' | ||||||
|  | 
 | ||||||
|  | 	def save(self, *args, **kwargs): | ||||||
|  | 		super().save(*args, **kwargs) | ||||||
|  | 		# *Node.save() aktualizuje název *Nodu. | ||||||
|  | 		try: | ||||||
|  | 			self.ulohazadaninode.save() | ||||||
|  | 		except ObjectDoesNotExist: | ||||||
|  | 			# Neexistující *Node nemá smysl aktualizovat. | ||||||
|  | 			pass | ||||||
|  | 		try: | ||||||
|  | 			self.ulohavzoraknode.save() | ||||||
|  | 		except ObjectDoesNotExist: | ||||||
|  | 			# Neexistující *Node nemá smysl aktualizovat. | ||||||
|  | 			pass | ||||||
|  | 
 | ||||||
|  | 	def cislo_node(self): | ||||||
|  | 		zadani_node = self.ulohazadaninode | ||||||
|  | 		from seminar.models.treenode import CisloNode | ||||||
|  | 		return treelib.get_upper_node_of_type(zadani_node, CisloNode) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 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) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Pohadka(SeminarModelBase): | ||||||
|  | 	"""Kus pohádky před/za úlohou v čísle""" | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_pohadky' | ||||||
|  | 		verbose_name = 'Pohádka' | ||||||
|  | 		verbose_name_plural = 'Pohádky' | ||||||
|  | 		ordering = ['vytvoreno'] | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key=True) | ||||||
|  | 
 | ||||||
|  | 	autor = models.ForeignKey( | ||||||
|  | 		Organizator, | ||||||
|  | 		verbose_name="Autor pohádky", | ||||||
|  | 
 | ||||||
|  | 		# Při nahrávání z TeXu není vyplnění vyžadováno, v adminu je | ||||||
|  | 		null=True, | ||||||
|  | 		blank=False, | ||||||
|  | 		on_delete=models.SET_NULL, | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	vytvoreno = models.DateTimeField( | ||||||
|  | 		'Vytvořeno', | ||||||
|  | 		default=timezone.now, | ||||||
|  | 		blank=True, | ||||||
|  | 		editable=False | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		uryvek = self.text if len(self.text) < 50 else self.text[:(50-3)]+"..." | ||||||
|  | 		return uryvek | ||||||
|  | 
 | ||||||
|  | 	def save(self, *args, **kwargs): | ||||||
|  | 		super().save(*args, **kwargs) | ||||||
|  | 		# *Node.save() aktualizuje název *Nodu. | ||||||
|  | 		try: | ||||||
|  | 			self.pohadkanode.save() | ||||||
|  | 		except ObjectDoesNotExist: | ||||||
|  | 			# Neexistující *Node nemá smysl aktualizovat. | ||||||
|  | 			pass | ||||||
|  | 
 | ||||||
							
								
								
									
										13
									
								
								various/migrations/0004_tvorba_pre.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								various/migrations/0004_tvorba_pre.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 01:06 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('various', '0003_fix_permissions'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |     ] | ||||||
							
								
								
									
										20
									
								
								various/migrations/0005_tvorba_relink.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								various/migrations/0005_tvorba_relink.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 13:18 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('tvorba', '0001_tvorba_create'), | ||||||
|  |         ('various', '0004_tvorba_pre'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='nastaveni', | ||||||
|  |             name='aktualni_cislo', | ||||||
|  |             field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='tvorba.cislo', verbose_name='Aktuální číslo'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										14
									
								
								various/migrations/0006_tvorba_post.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								various/migrations/0006_tvorba_post.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-10-30 21:35 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('various', '0005_tvorba_relink'), | ||||||
|  |         ('tvorba', '0003_tvorba_post'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |     ] | ||||||
|  | @ -3,7 +3,7 @@ from django.db import models | ||||||
| from reversion import revisions as reversion | from reversion import revisions as reversion | ||||||
| from solo.models import SingletonModel | from solo.models import SingletonModel | ||||||
| 
 | 
 | ||||||
| from seminar.models import Cislo | from tvorba.models import Cislo | ||||||
| 
 | 
 | ||||||
| from django.urls import reverse | from django.urls import reverse | ||||||
| 
 | 
 | ||||||
|  | @ -33,7 +33,7 @@ class Nastaveni(SingletonModel): | ||||||
| 		return 'Nastavení semináře' | 		return 'Nastavení semináře' | ||||||
| 
 | 
 | ||||||
| 	def admin_url(self): | 	def admin_url(self): | ||||||
| 		return reverse('admin:seminar_nastaveni_change', args=(self.id, )) | 		return reverse('admin:various_nastaveni_change', args=(self.id, )) | ||||||
| 	 | 	 | ||||||
| 	def verejne(self): | 	def verejne(self): | ||||||
| 		return False | 		return False | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue