Podezřelé semináře (#1465) #65
					 33 changed files with 1540 additions and 749 deletions
				
			
		|  | @ -216,57 +216,57 @@ | |||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "add_cislo", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "cislo" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "change_cislo", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "cislo" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "delete_cislo", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "cislo" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "view_cislo", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "cislo" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "add_clanek", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "clanek" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "change_clanek", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "clanek" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "delete_clanek", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "clanek" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "view_clanek", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "clanek" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "add_deadline", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "deadline" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "change_deadline", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "deadline" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "view_deadline", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "deadline" | ||||
| 	}, | ||||
| 	{ | ||||
|  | @ -371,22 +371,22 @@ | |||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "add_pohadka", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "pohadka" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "change_pohadka", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "pohadka" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "delete_pohadka", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "pohadka" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "view_pohadka", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "pohadka" | ||||
| 	}, | ||||
| 	{ | ||||
|  | @ -411,22 +411,22 @@ | |||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "add_problem", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "problem" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "change_problem", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "problem" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "delete_problem", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "problem" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "view_problem", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "problem" | ||||
| 	}, | ||||
| 	{ | ||||
|  | @ -441,22 +441,22 @@ | |||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "add_rocnik", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "rocnik" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "change_rocnik", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "rocnik" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "delete_rocnik", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "rocnik" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "view_rocnik", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "rocnik" | ||||
| 	}, | ||||
| 	{ | ||||
|  | @ -541,42 +541,42 @@ | |||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "add_tema", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "tema" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "change_tema", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "tema" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "delete_tema", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "tema" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "view_tema", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "tema" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "add_uloha", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "uloha" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "change_uloha", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "uloha" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "delete_uloha", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"ct_model": "uloha" | ||||
| 	}, | ||||
| 	{ | ||||
| 		"codename": "view_uloha", | ||||
| 		"ct_app_label": "seminar", | ||||
| 		"ct_app_label": "tvorba", | ||||
| 		"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.conf import settings | ||||
| 
 | ||||
| import seminar.models as am # tvorba | ||||
| import tvorba.models as am | ||||
| from seminar.models import base as bm | ||||
| 
 | ||||
| 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> | ||||
| 
 | ||||
| <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> | ||||
| 	<ul> | ||||
| 		<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 novinky.models import Novinky | ||||
| 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 | ||||
| from soustredeni.models import generate_filename_konfera | ||||
| # migr. 0001 | ||||
| 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__) | ||||
| 
 | ||||
| from seminar.models import tvorba as am | ||||
| import tvorba.models as am | ||||
| 
 | ||||
| class TreeNode(PolymorphicModel): | ||||
| 	class Meta: | ||||
|  |  | |||
|  | @ -1,40 +1,7 @@ | |||
| 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.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__) | ||||
| 
 | ||||
|  | @ -45,677 +12,3 @@ class OverwriteStorage(FileSystemStorage): | |||
| 		if self.exists(name): | ||||
| 			os.remove(os.path.join(self.location,name)) | ||||
| 		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 seminar.models.base import SeminarModelBase | ||||
| import seminar.models as am # tvorba | ||||
| import tvorba.models as am | ||||
| 
 | ||||
| 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.forms import ModelForm | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.urls import reverse | ||||
| 
 | ||||
| from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter | ||||
| from django.utils.safestring import mark_safe | ||||
|  | @ -9,7 +10,7 @@ from django.utils.safestring import mark_safe | |||
| 
 | ||||
| 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(ZmrazenaVysledkovka) | ||||
|  | @ -59,9 +60,6 @@ class CisloForm(ModelForm): | |||
| 		# 			if problem not in \ | ||||
| 		# 				(Problem.STAV_ZADANY, Problem.STAV_VYRESENY): | ||||
| 		# 				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 = [] | ||||
| 		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})) | ||||
| 		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>'))) | ||||
| 				'<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: | ||||
| 			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 solo.models import SingletonModel | ||||
| 
 | ||||
| from seminar.models import Cislo | ||||
| from tvorba.models import Cislo | ||||
| 
 | ||||
| from django.urls import reverse | ||||
| 
 | ||||
|  | @ -33,7 +33,7 @@ class Nastaveni(SingletonModel): | |||
| 		return 'Nastavení semináře' | ||||
| 
 | ||||
| 	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): | ||||
| 		return False | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue