349 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			349 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import logging
 | ||
| 
 | ||
| from django.db import models
 | ||
| from django.urls import reverse
 | ||
| from django.contrib.contenttypes.models import ContentType
 | ||
| 
 | ||
| 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 various.models import SeminarModelBase
 | ||
| 
 | ||
| from personalni.models import Organizator
 | ||
| from odevzdavatko.models import Reseni
 | ||
| 
 | ||
| logger = logging.getLogger(__name__)
 | ||
| 
 | ||
| import tvorba.models as am
 | ||
| 
 | ||
| class TreeNode(PolymorphicModel):
 | ||
| 	class Meta:
 | ||
| 		db_table = "seminar_nodes_treenode"
 | ||
| 		verbose_name = "TreeNode"
 | ||
| 		verbose_name_plural = "TreeNody"
 | ||
| 
 | ||
| 	# TODO: Nechceme radši jako root vyžadovat přímo RocnikNode?
 | ||
| 	root = models.ForeignKey('TreeNode',
 | ||
| 		related_name="potomci_set",
 | ||
| 		null = True,
 | ||
| 		blank = False,
 | ||
| 		on_delete = models.SET_NULL, # Vrcholy s null kořenem jsou sirotci bez ročníku 
 | ||
| 		verbose_name="kořen stromu")
 | ||
| 	first_child = models.OneToOneField('TreeNode',
 | ||
| 		related_name='father_of_first',
 | ||
| 		null = True,
 | ||
| 		blank = True,
 | ||
| 		on_delete=models.SET_NULL,
 | ||
| 		verbose_name="první potomek")
 | ||
| 	succ = models.OneToOneField('TreeNode',
 | ||
| 		related_name="prev",
 | ||
| 		null = True,
 | ||
| 		blank = True,
 | ||
| 		on_delete=models.SET_NULL,
 | ||
| 		verbose_name="další element na stejné úrovni")
 | ||
| 	nazev = models.TextField("název tohoto node",
 | ||
| 		help_text = "Tento název se zobrazuje v nabídkách pro výběr vhodného TreeNode",
 | ||
| 		blank=False, 
 | ||
| 		null=True) # Nezveřejnitelný název na stránky - pouze do adminu
 | ||
| 	zajimave = models.BooleanField(default = False,
 | ||
| 		verbose_name = "Zajímavé",
 | ||
| 		help_text = "Zobrazí se daná věc na rozcestníku témátek")
 | ||
| 	srolovatelne = models.BooleanField(null = True, blank = True,
 | ||
| 		verbose_name = "Srolovatelné",
 | ||
| 		help_text = "Bude na stránce témátka možnost tuto položku skrýt")
 | ||
| 
 | ||
| 	def getOdkazStr(self): # String na rozcestník
 | ||
| 		return self.first_child.getOdkazStr()
 | ||
| 
 | ||
| 	def getOdkaz(self): # ID HTML tagu, na který se bude scrollovat #{{self.getOdkaz}}
 | ||
| 	# Jsem si vědom, že tu potenciálně vznikají kolize.
 | ||
| 	# Přijdou mi natolik nepravděpodobné, že je neřeším
 | ||
| 	# Chtěl jsem ale hezké odkazy
 | ||
| 		string = unidecode(self.getOdkazStr())
 | ||
| 		returnVal = ""
 | ||
| 		i = 0	
 | ||
| 		while len(returnVal) < 16: # Max 15 znaků
 | ||
| 			if i == len(string):
 | ||
| 				break
 | ||
| 			if string[i] == " ":
 | ||
| 				returnVal += "-"
 | ||
| 			if string[i].isalnum():
 | ||
| 				returnVal += string[i].lower()
 | ||
| 			i += 1
 | ||
| 		return returnVal
 | ||
| 
 | ||
| 	def __str__(self):
 | ||
| 		if self.nazev:
 | ||
| 			return self.nazev
 | ||
| 		else:
 | ||
| 			#TODO: logování
 | ||
| 			return "Nepojmenovaný Treenode"
 | ||
| 	
 | ||
| 	def save(self, *args, **kwargs):
 | ||
| 		self.aktualizuj_nazev()
 | ||
| 		super().save(*args, **kwargs)
 | ||
| 
 | ||
| 	def aktualizuj_nazev(self):
 | ||
| 		raise NotImplementedError("Pokus o aktualizaci názvu obecného TreeNode místo konkrétní instance")
 | ||
| 
 | ||
| 	def get_admin_url(self):
 | ||
| 		content_type = ContentType.objects.get_for_model(self.__class__)
 | ||
| 		return reverse("admin:%s_%s_change" % (content_type.app_label, content_type.model), args=(self.id,))
 | ||
| 
 | ||
| class RocnikNode(TreeNode):
 | ||
| 	class Meta:
 | ||
| 		db_table = 'seminar_nodes_rocnik'
 | ||
| 		verbose_name = 'Ročník (Node)'
 | ||
| 		verbose_name_plural = 'Ročníky (Node)'
 | ||
| 	rocnik = models.OneToOneField(am.Rocnik,
 | ||
| 		on_delete = models.PROTECT, # Pokud chci mazat ročník, musím si Node pořešit ručně
 | ||
| 		verbose_name = "ročník")
 | ||
| 
 | ||
| 	def aktualizuj_nazev(self):
 | ||
| 		self.nazev = "RocnikNode: "+str(self.rocnik)
 | ||
| 
 | ||
| class CisloNode(TreeNode):
 | ||
| 	class Meta:
 | ||
| 		db_table = 'seminar_nodes_cislo'
 | ||
| 		verbose_name = 'Číslo (Node)'
 | ||
| 		verbose_name_plural = 'Čísla (Node)'
 | ||
| 	cislo = models.OneToOneField(am.Cislo,
 | ||
| 		on_delete = models.PROTECT, # Pokud chci mazat číslo, musím si Node pořešit ručně
 | ||
| 		verbose_name = "číslo")
 | ||
| 
 | ||
| 	def aktualizuj_nazev(self):
 | ||
| 		self.nazev = "CisloNode: "+str(self.cislo)
 | ||
| 
 | ||
| 	def getOdkazStr(self):
 | ||
| 		return "Číslo " + str(self.cislo)
 | ||
| 
 | ||
| class MezicisloNode(TreeNode):
 | ||
| 	class Meta:
 | ||
| 		db_table = 'seminar_nodes_mezicislo'
 | ||
| 		verbose_name = 'Mezičíslo (Node)'
 | ||
| 		verbose_name_plural = 'Mezičísla (Node)'
 | ||
| 
 | ||
| 	# TODO: Využít TreeLib
 | ||
| 	def aktualizuj_nazev(self):
 | ||
| 		from treenode.treelib import safe_pred
 | ||
| 		if safe_pred(self) is not None:
 | ||
| 			if (self.prev.get_real_instance_class() != CisloNode and
 | ||
| 				self.prev.get_real_instance_class() != MezicisloNode):
 | ||
| 					raise ValueError("Předchůdce není číslo!")
 | ||
| 			posledni = self.prev.cislo
 | ||
| 			self.nazev = "MezicisloNode: Mezičíslo po čísle"+str(posledni)
 | ||
| 		elif self.root:
 | ||
| 			if self.root.get_real_instance_class() != RocnikNode:
 | ||
| 				raise ValueError("Kořen stromu není ročník!")
 | ||
| 			rocnik = self.root.rocnik
 | ||
| 			self.nazev = "MezicisloNode: První mezičíslo ročníku "+str(rocnik)
 | ||
| 		else:
 | ||
| 			print("!!!!! Nějaké neidentifikované mezičíslo !!!!!")
 | ||
| 			self.nazev = "MezicisloNode: Neidentifikovatelné mezičíslo!"
 | ||
| 	def getOdkazStr(self):
 | ||
| 		return "Obsah dostupný pouze na webu"
 | ||
| 
 | ||
| class TemaVCisleNode(TreeNode):
 | ||
| 	""" Obsahuje příspěvky k tématu v daném čísle """
 | ||
| 	class Meta:
 | ||
| 		db_table = 'seminar_nodes_temavcisle'
 | ||
| 		verbose_name = 'Téma v čísle (Node)'
 | ||
| 		verbose_name_plural = 'Témata v čísle (Node)'
 | ||
| 	tema = models.ForeignKey(am.Tema,
 | ||
| 		on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně
 | ||
| 		verbose_name = "téma v čísle")
 | ||
| 
 | ||
| 	def aktualizuj_nazev(self):
 | ||
| 		self.nazev = "TemaVCisleNode: "+str(self.tema)
 | ||
| 
 | ||
| 	def getOdkazStr(self):
 | ||
| 		return str(self.tema)
 | ||
| 
 | ||
| class OrgTextNode(TreeNode):
 | ||
| 	class Meta:
 | ||
| 		db_table = 'seminar_nodes_orgtextnode'
 | ||
| 		verbose_name = 'Organizátorský článek (Node)'
 | ||
| 		verbose_name_plural = 'Organizátorské články (Node)'
 | ||
| 	
 | ||
| 	organizator = models.ForeignKey(Organizator,
 | ||
| 		null=False,
 | ||
| 		blank=False,
 | ||
| 		on_delete=models.DO_NOTHING,
 | ||
| 		verbose_name="Organizátor",
 | ||
| 		)
 | ||
| 	org_verejny = models.BooleanField(default = True,
 | ||
| 		verbose_name = "Org je veřejný?",
 | ||
| 		help_text = "Pokud ano, bude org pod článkem podepsaný",
 | ||
| 		null=False,
 | ||
| 		)
 | ||
| 
 | ||
| 	def aktualizuj_nazev(self):
 | ||
| 		return f"OrgTextNode začínající následujícim: {self.first_child.nazev}"
 | ||
| 
 | ||
| 	# FIXME!!!
 | ||
| 	#def getOdkazStr(self):
 | ||
| 	#	return str(self.clanek)
 | ||
| 
 | ||
| 
 | ||
| class UlohaZadaniNode(TreeNode):
 | ||
| 	class Meta:
 | ||
| 		db_table = 'seminar_nodes_uloha_zadani'
 | ||
| 		verbose_name = 'Zadání úlohy (Node)'
 | ||
| 		verbose_name_plural = 'Zadání úloh (Node)'
 | ||
| 	uloha = models.OneToOneField(am.Uloha,
 | ||
| 		on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně
 | ||
| 		verbose_name = "úloha",
 | ||
| 		null=True,
 | ||
| 		blank=False)
 | ||
| 
 | ||
| 	def aktualizuj_nazev(self):
 | ||
| 		self.nazev = "UlohaZadaniNode: "+str(self.uloha)
 | ||
| 
 | ||
| 	def getOdkazStr(self):
 | ||
| 		return str(self.uloha)
 | ||
| 
 | ||
| 
 | ||
| class PohadkaNode(TreeNode):
 | ||
| 	class Meta:
 | ||
| 		db_table = 'seminar_nodes_pohadka'
 | ||
| 		verbose_name = 'Pohádka (Node)'
 | ||
| 		verbose_name_plural = 'Pohádky (Node)'
 | ||
| 	pohadka = models.OneToOneField(am.Pohadka,
 | ||
| 		on_delete=models.PROTECT, # Pokud chci mazat pohádku, musím si Node pořešit ručně
 | ||
| 		verbose_name = "pohádka",
 | ||
| 		)
 | ||
| 
 | ||
| 	def aktualizuj_nazev(self):
 | ||
| 		self.nazev = "PohadkaNode: "+str(self.pohadka)
 | ||
| 
 | ||
| class UlohaVzorakNode(TreeNode):
 | ||
| 	class Meta:
 | ||
| 		db_table = 'seminar_nodes_uloha_vzorak'
 | ||
| 		verbose_name = 'Vzorák úlohy (Node)'
 | ||
| 		verbose_name_plural = 'Vzoráky úloh (Node)'
 | ||
| 	uloha = models.OneToOneField(am.Uloha,
 | ||
| 		on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně
 | ||
| 		verbose_name = "úloha",
 | ||
| 		null=True,
 | ||
| 		blank=False)
 | ||
| 
 | ||
| 	def aktualizuj_nazev(self):
 | ||
| 		self.nazev = "UlohaVzorakNode: "+str(self.uloha)
 | ||
| 
 | ||
| 	def getOdkazStr(self):
 | ||
| 		return str(self.uloha)
 | ||
| 
 | ||
| class ReseniNode(TreeNode):
 | ||
| 	class Meta:
 | ||
| 		db_table = 'seminar_nodes_otistene_reseni'
 | ||
| 		verbose_name = 'Otištěné řešení (Node)'
 | ||
| 		verbose_name_plural = 'Otištěná řešení (Node)'
 | ||
| 	reseni = models.ForeignKey(Reseni,
 | ||
| 							   on_delete=models.PROTECT,
 | ||
| 							   verbose_name = 'reseni')
 | ||
| 
 | ||
| 	def aktualizuj_nazev(self):
 | ||
| 		self.nazev = "ReseniNode: "+str(self.reseni)
 | ||
| 
 | ||
| 	def getOdkazStr(self):
 | ||
| 		return str(self.reseni)
 | ||
| 
 | ||
| # LEdoian: Můžu prostě odstřelit Text a Obrázek do aplikace `treenode`, kam podle mě
 | ||
| # stejně patří? (Myšlenka, proč by tam měly patřit: tak, jak teď jsou je stejně
 | ||
| # využívají jen TreeNody a žádné rozhraní k nim stejně není, takže aktuálně
 | ||
| # použít nejdou (jako zbytek TN) a jejich sémantika pro „společnou tvorbu čísla
 | ||
| # na web a PDF“ je ze stejné školy. A taky kvůli nemíchání – pokud vbrzku bude
 | ||
| # potřeba nějaký podobný model, navrhuji ho udělat znovu a klidně úplně stejně,
 | ||
| # staré věci pak buď zůstanou skryté, nebo je datově namigrujeme – taková
 | ||
| # migrace bude snadná.) – Jidáš: jo, a napiš tam tyhle myšlenky do komentáře.
 | ||
| # (zhruba přepis diskuse ve web-dev, 2024-10-30.)
 | ||
| 
 | ||
| class Text(SeminarModelBase):
 | ||
| 	class Meta:
 | ||
| 		db_table = 'seminar_texty'
 | ||
| 		verbose_name = 'text'
 | ||
| 		verbose_name_plural = 'texty'
 | ||
| 
 | ||
| 	na_web = models.TextField(
 | ||
| 		'text na web', blank=True,
 | ||
| 		help_text='Text ke zveřejnění na webu')
 | ||
| 
 | ||
| 	do_cisla = models.TextField(
 | ||
| 		'text do čísla', blank=True,
 | ||
| 		help_text='Text ke zveřejnění v čísle')
 | ||
| 
 | ||
| 	# má OneToOneField s:
 | ||
| 	# Reseni (je u něj jako reseni_cele)
 | ||
| 
 | ||
| 	# obrázky mají návaznost opačným směrem (vazba z druhé strany)
 | ||
| 
 | ||
| 	def save(self, *args, **kwargs):
 | ||
| 		super().save(*args, **kwargs)
 | ||
| 		# *Node.save() aktualizuje název *Nodu.
 | ||
| 		for tn in self.textnode_set.all():
 | ||
| 			tn.save()
 | ||
| 
 | ||
| 	def __str__(self):
 | ||
| 		return str(self.na_web)[:20]
 | ||
| 
 | ||
| class TextNode(TreeNode):
 | ||
| 	class Meta:
 | ||
| 		db_table = 'seminar_nodes_obsah'
 | ||
| 		verbose_name = 'Text (Node)'
 | ||
| 		verbose_name_plural = 'Text (Node)'
 | ||
| 	text = models.ForeignKey(Text,
 | ||
| 		on_delete=models.CASCADE,
 | ||
| 		verbose_name = 'text')
 | ||
| 	
 | ||
| 	def aktualizuj_nazev(self):
 | ||
| 		self.nazev = "TextNode: "+str(self.text)
 | ||
| 
 | ||
| 	def getOdkazStr(self):
 | ||
| 		return str(self.text)
 | ||
| 
 | ||
| class CastNode(TreeNode):
 | ||
| 	class Meta:
 | ||
| 		db_table = 'seminar_nodes_cast'
 | ||
| 		verbose_name = 'Část (Node)'
 | ||
| 		verbose_name_plural = 'Části (Node)'
 | ||
| 	
 | ||
| 	nadpis = models.CharField('Nadpis', max_length=100, help_text = 'Nadpis podvěšené části obsahu')
 | ||
| 
 | ||
| 	def aktualizuj_nazev(self):
 | ||
| 		self.nazev = "CastNode: "+str(self.nadpis)
 | ||
| 
 | ||
| 	def getOdkazStr(self):
 | ||
| 		return str(self.nadpis)
 | ||
| 
 | ||
| 
 | ||
| class Obrazek(SeminarModelBase):
 | ||
| 	class Meta:
 | ||
| 		db_table = 'seminar_obrazky'
 | ||
| 		verbose_name = 'obrázek'
 | ||
| 		verbose_name_plural = 'obrázky'
 | ||
| 
 | ||
| 	# Interní ID
 | ||
| 	id = models.AutoField(primary_key=True)
 | ||
| 
 | ||
| 	na_web = models.ImageField(
 | ||
| 		'obrázek na web', upload_to='obrazky/%Y/%m/%d/',
 | ||
| 		null=True, blank=True)
 | ||
| 
 | ||
| 	text = models.ForeignKey(
 | ||
| 		Text, verbose_name='text',
 | ||
| 		help_text='text, ve kterém se obrázek vyskytuje',
 | ||
| 		null=False, blank=False, on_delete=models.CASCADE)
 | ||
| 
 | ||
| 	do_cisla_barevny = models.FileField(
 | ||
| 		'barevný obrázek do čísla',
 | ||
| 		help_text='Barevná verze obrázku do čísla',
 | ||
| 		upload_to='obrazky/%Y/%m/%d/', blank=True, null=True)
 | ||
| 
 | ||
| 	do_cisla_cernobily = models.FileField(
 | ||
| 		'černobílý obrázek do čísla',
 | ||
| 		help_text='Černobílá verze obrázku do čísla',
 | ||
| 		upload_to='obrazky/%Y/%m/%d/', blank=True, null=True)
 | ||
| 
 | ||
| 	# TODO placement hint - chci ho tady / pred textem / za textem
 | ||
| 
 |