Jonas Havelka
3 years ago
26 changed files with 599 additions and 534 deletions
@ -0,0 +1,11 @@ |
|||||
|
""" |
||||
|
Obsahuje vše, co se týká odevzdávání (+ nahrávání) a opravování řešení řešitelů. |
||||
|
|
||||
|
Slovníček: |
||||
|
Moje řešení = Přehled řešení = Řešení, která odevzdal aktuálního uživatel sám. |
||||
|
Došlá řešení = Tabulka + seznam + detail + ... = Řešení, která poslal někdo jiný. |
||||
|
Poslat řešení = Odevdat mé řešení. (Tj. řešení se vztahem k aktuálnímu uživateli.) |
||||
|
Nahrát řešení = Nahrání řešení bez vztahu k aktuálnímu uživateli. |
||||
|
|
||||
|
TODO: Místo vložit řešení v nahrávání a posílání řešení dát něco jiného? |
||||
|
""" |
@ -0,0 +1,28 @@ |
|||||
|
from django.contrib import admin |
||||
|
from django_reverse_admin import ReverseModelAdmin |
||||
|
import seminar.models as m |
||||
|
|
||||
|
|
||||
|
class PrilohaReseniInline(admin.TabularInline): |
||||
|
model = m.PrilohaReseni |
||||
|
extra = 1 |
||||
|
|
||||
|
|
||||
|
class Reseni_ResiteleInline(admin.TabularInline): |
||||
|
model = m.Reseni_Resitele |
||||
|
|
||||
|
|
||||
|
@admin.register(m.Reseni) |
||||
|
class ReseniAdmin(ReverseModelAdmin): |
||||
|
base_model = m.Reseni |
||||
|
inline_type = 'tabular' |
||||
|
# inline_reverse = ['text_cely','resitele'] TODO vrátit zpět a zrychlit dotaz |
||||
|
inline_reverse = ['resitele'] |
||||
|
exclude = ['text_zkraceny', 'text_zkraceny_set'] |
||||
|
inlines = [PrilohaReseniInline] |
||||
|
# FAIL in template |
||||
|
# inlines = [PrilohaReseniInline,Reseni_ResiteleInline] |
||||
|
|
||||
|
|
||||
|
admin.site.register(m.PrilohaReseni) |
||||
|
admin.site.register(m.Hodnoceni) |
@ -0,0 +1,5 @@ |
|||||
|
from django.apps import AppConfig |
||||
|
|
||||
|
|
||||
|
class OdevzdavatkoConfig(AppConfig): |
||||
|
name = 'odevzdavatko' |
@ -0,0 +1,218 @@ |
|||||
|
from django import forms |
||||
|
from dal import autocomplete |
||||
|
from django.forms import formset_factory |
||||
|
from django.forms.models import inlineformset_factory |
||||
|
|
||||
|
from seminar.models import Resitel |
||||
|
import seminar.models as m |
||||
|
|
||||
|
import logging |
||||
|
|
||||
|
# pro přidání políčka do formuláře je potřeba |
||||
|
# - mít v modelu tu položku, kterou chci upravovat |
||||
|
# - přidat do views (prihlaskaView, resitelEditView) |
||||
|
# - přidat do forms |
||||
|
# - includovat do html |
||||
|
|
||||
|
class DateInput(forms.DateInput): |
||||
|
# aby se datum dalo vybírat z kalendáře |
||||
|
input_type = 'date' |
||||
|
|
||||
|
|
||||
|
class PosliReseniForm(forms.Form): |
||||
|
#FIXME jen podproblémy daného problému |
||||
|
problem = forms.ModelChoiceField(label='Problém',queryset=m.Problem.objects.all()) |
||||
|
# to_field_name |
||||
|
#problem = models.ManyToManyField(Problem, verbose_name='problém', help_text='Problém', |
||||
|
# through='Hodnoceni') |
||||
|
|
||||
|
# FIXME pridat vice resitelu |
||||
|
resitel = forms.ModelChoiceField(label="Řešitel", |
||||
|
queryset=Resitel.objects.all(), |
||||
|
widget=autocomplete.ModelSelect2( |
||||
|
url='autocomplete_resitel', |
||||
|
attrs = {'data-placeholder--id': '-1', |
||||
|
'data-placeholder--text' : '---', |
||||
|
'data-allow-clear': 'true'}) |
||||
|
) |
||||
|
|
||||
|
|
||||
|
#resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení', |
||||
|
# help_text='Seznam autorů řešení', through='Reseni_Resitele') |
||||
|
|
||||
|
cas_doruceni = forms.DateField(widget=DateInput(),label="Čas doručení") |
||||
|
|
||||
|
#cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True) |
||||
|
|
||||
|
forma = forms.ChoiceField(label="Forma řešení",choices = m.Reseni.FORMA_CHOICES) |
||||
|
#forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False, |
||||
|
# default=FORMA_EMAIL) |
||||
|
|
||||
|
poznamka = forms.CharField(label='Neveřejná poznámka', required=False) |
||||
|
#poznamka = models.TextField('neveřejná poznámka', blank=True, |
||||
|
# help_text='Neveřejná poznámka k řešení (plain text)') |
||||
|
|
||||
|
#TODO body do cisla |
||||
|
#TODO prilohy |
||||
|
|
||||
|
##def __init__(self, *args, **kwargs): |
||||
|
## super().__init__(*args, **kwargs) |
||||
|
## #self.fields['favorite_color'] = forms.ChoiceField(choices=[(color.id, color.name) for color in Resitel.objects.all()]) |
||||
|
|
||||
|
class NahrajReseniForm(forms.ModelForm): |
||||
|
class Meta: |
||||
|
model = m.Reseni |
||||
|
fields = ('problem',) |
||||
|
help_texts = {'problem':''} # Nezobrazovat help text ve formuláři |
||||
|
|
||||
|
widgets = {'problem': |
||||
|
autocomplete.ModelSelect2Multiple( |
||||
|
url='autocomplete_problem_odevzdatelny', |
||||
|
attrs = {'data-placeholder--id': '-1', |
||||
|
'data-placeholder--text' : '---', |
||||
|
'data-allow-clear': 'true'}, |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni, |
||||
|
form = NahrajReseniForm, |
||||
|
fields = ('soubor','res_poznamka'), |
||||
|
widgets = {'res_poznamka':forms.TextInput()}, |
||||
|
extra = 1, |
||||
|
can_delete = False, |
||||
|
|
||||
|
) |
||||
|
|
||||
|
|
||||
|
class JednoHodnoceniForm(forms.ModelForm): |
||||
|
class Meta: |
||||
|
model = m.Hodnoceni |
||||
|
fields = ('problem', 'body', 'cislo_body') |
||||
|
widgets = { |
||||
|
'problem': autocomplete.ModelSelect2( |
||||
|
url='autocomplete_problem_odevzdatelny', # FIXME: Dovolit i starší? |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
OhodnoceniReseniFormSet = formset_factory(JednoHodnoceniForm, |
||||
|
extra = 0, |
||||
|
) |
||||
|
|
||||
|
class PoznamkaReseniForm(forms.ModelForm): |
||||
|
class Meta: |
||||
|
model = m.Reseni |
||||
|
fields = ('poznamka',) |
||||
|
|
||||
|
# FIXME: Ideálně by mělo být součástí třídy níž, ale neumím to udělat |
||||
|
DATE_FORMAT = '%Y-%m-%d' |
||||
|
|
||||
|
class OdevzdavatkoTabulkaFiltrForm(forms.Form): |
||||
|
"""Form pro filtrování přehledové odevzdávátkové tabulky |
||||
|
|
||||
|
Inspirováno https://kam.mff.cuni.cz/mffzoom/""" |
||||
|
|
||||
|
# Věci definované níž se importují i ve views pro odevzdávátko (Inspirováno https://docs.djangoproject.com/en/3.1/ref/models/fields/#field-choices) |
||||
|
|
||||
|
RESITELE_RELEVANTNI = 'relevantni' |
||||
|
RESITELE_NEODMATUROVAVSI = 'neodmaturovavsi' |
||||
|
RESITELE_CHOICES = [ |
||||
|
(RESITELE_RELEVANTNI, 'Relevantní řešitelé'), # I.e. nezobrazovat prázdné řádky tabulky |
||||
|
(RESITELE_NEODMATUROVAVSI, 'Všichni bez maturity'), |
||||
|
# Možná: všechny vč. historických? |
||||
|
] |
||||
|
|
||||
|
PROBLEMY_MOJE = 'moje' |
||||
|
PROBLEMY_LETOSNI = 'letosni' |
||||
|
PROBLEMY_CHOICES = [ |
||||
|
(PROBLEMY_MOJE, 'Moje problémy'), # Letošní problémy, které mají v sobě nebo v nadproblémech přiřazeného daného orga |
||||
|
(PROBLEMY_LETOSNI, 'Všechny letošní'), |
||||
|
# TODO: *hlavní problémy, možná všechny... |
||||
|
# XXX: Chtělo by to i "aktuálně zadané... |
||||
|
] |
||||
|
|
||||
|
# TODO: Typy problémů (problémy, úlohy, ostatní, všechny)? Jen některá řešení (obodovaná/neobodovaná, víc řešitelů, ...)? |
||||
|
|
||||
|
|
||||
|
@classmethod |
||||
|
def gen_terminy(cls, rocnik=None): |
||||
|
import datetime |
||||
|
from time import strftime |
||||
|
|
||||
|
from django.db.utils import OperationalError |
||||
|
try: |
||||
|
aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik |
||||
|
aktualni_cislo = m.Nastaveni.get_solo().aktualni_cislo |
||||
|
except OperationalError: |
||||
|
# django.db.utils.OperationalError: no such table: seminar_nastaveni |
||||
|
# Nemáme databázi, takže to selhalo. Pro jistotu vrátíme aspoň dvě možnosti, ať to nepadá dál |
||||
|
logger = logging.getLogger(__name__) |
||||
|
logger.error("Rozbitá databáze (před počátečními migracemi?)") |
||||
|
return [('broken', 'Je to rozbitý'), ('fubar', 'Nefunguje to')] |
||||
|
|
||||
|
# FIXME: Tohle je hnusný monkey patch, mělo by to být nějak zahrnuto výš. |
||||
|
if rocnik is not None: |
||||
|
aktualni_rocnik = rocnik |
||||
|
aktualni_cislo = m.Cislo.objects.filter(rocnik=rocnik).order_by('poradi').last() |
||||
|
|
||||
|
result = [] |
||||
|
|
||||
|
for cislo in m.Cislo.objects.filter( |
||||
|
rocnik=aktualni_rocnik, |
||||
|
poradi__lte=aktualni_cislo.poradi, |
||||
|
).reverse(): # Standardně se řadí od nejnovějšího čísla |
||||
|
# Předem je mi líto kohokoliv, kdo tyhle řádky bude číst... |
||||
|
if cislo.datum_vydani is not None and cislo.datum_vydani <= datetime.date.today(): |
||||
|
result.append(( |
||||
|
strftime(DATE_FORMAT, cislo.datum_vydani.timetuple()), |
||||
|
f"Vydání {cislo.poradi}. čísla")) |
||||
|
if cislo.datum_preddeadline is not None and cislo.datum_preddeadline <= datetime.date.today(): |
||||
|
result.append(( |
||||
|
strftime(DATE_FORMAT, cislo.datum_preddeadline.timetuple()), |
||||
|
f"Předdeadline {cislo.poradi}. čísla")) |
||||
|
if cislo.datum_deadline_soustredeni is not None and cislo.datum_deadline_soustredeni <= datetime.date.today(): |
||||
|
result.append(( |
||||
|
strftime(DATE_FORMAT, cislo.datum_deadline_soustredeni.timetuple()), |
||||
|
f"Sous. deadline {cislo.poradi}. čísla")) |
||||
|
if cislo.datum_deadline is not None and cislo.datum_deadline <= datetime.date.today(): |
||||
|
result.append(( |
||||
|
strftime(DATE_FORMAT, cislo.datum_deadline.timetuple()), |
||||
|
f"Finální deadline {cislo.poradi}. čísla")) |
||||
|
result.append(( |
||||
|
strftime(DATE_FORMAT, datetime.date.today().timetuple()), f"Dnes")) |
||||
|
|
||||
|
return result |
||||
|
|
||||
|
@classmethod |
||||
|
def gen_initial(cls, rocnik=None): |
||||
|
terminy = cls.gen_terminy(rocnik) |
||||
|
initial = { |
||||
|
'resitele': cls.RESITELE_RELEVANTNI, |
||||
|
'problemy': cls.PROBLEMY_MOJE, |
||||
|
# Pokud chceme neaktuální ročník, tak nás nejspíš zajímají všechna řešení… |
||||
|
'reseni_od': terminy[-2] if rocnik is None else terminy[0], |
||||
|
'reseni_do': terminy[-1], |
||||
|
'neobodovane': False, |
||||
|
} |
||||
|
return initial |
||||
|
|
||||
|
def __init__(self, *args, rocnik=None, **kwargs): |
||||
|
if 'initial' not in kwargs: |
||||
|
super().__init__(initial=self.gen_initial(rocnik), *args, **kwargs) |
||||
|
else: |
||||
|
super().__init__(*args, **kwargs) |
||||
|
# choices jako parametr Select widgetu neumí brát callable, jen iterable, takže si pro jednoduchost můžu rovnou uložit výsledek sem... |
||||
|
# A "sem" znamená do libovolné metody, protože jinak se jedná o kód, který django spustí při inicializaci a protože potřebujeme databázi, tak by spadnul při vyrábění testdat... |
||||
|
self.terminy = self.gen_terminy(rocnik) |
||||
|
self.fields['reseni_od'].widget = forms.Select(choices=self.gen_terminy(rocnik)) |
||||
|
# Pokud chceme neaktuální ročník, tak nás nejspíš zajímají všechna řešení… |
||||
|
self.fields['reseni_od'].initial = self.terminy[-2] if rocnik is None else self.terminy[0] |
||||
|
self.fields['reseni_do'].widget = forms.Select(choices=self.gen_terminy(rocnik)) |
||||
|
self.fields['reseni_do'].initial = self.terminy[-1] |
||||
|
|
||||
|
# NOTE: Initial definuji pro jednotlivé fieldy, aby to bylo tady a nebylo potřeba to řešit ve views... |
||||
|
resitele = forms.ChoiceField(choices=RESITELE_CHOICES) |
||||
|
problemy = forms.ChoiceField(choices=PROBLEMY_CHOICES) |
||||
|
|
||||
|
reseni_od = forms.DateField(input_formats=[DATE_FORMAT]) |
||||
|
reseni_do = forms.DateField(input_formats=[DATE_FORMAT]) |
||||
|
neobodovane = forms.BooleanField(required=False) |
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 717 B |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@ -1,7 +1,7 @@ |
|||||
{% extends "base.html" %} |
{% extends "base.html" %} |
||||
{% load staticfiles %} |
{% load staticfiles %} |
||||
{% block script %} |
{% block script %} |
||||
<script src="{% static 'seminar/dynamic_formsets.js' %}"></script> |
<script src="{% static 'odevzdavatko/dynamic_formsets.js' %}"></script> |
||||
{% endblock %} |
{% endblock %} |
||||
|
|
||||
{% block content %} |
{% block content %} |
@ -0,0 +1,18 @@ |
|||||
|
from django.urls import path |
||||
|
|
||||
|
from seminar.utils import org_required, resitel_required, viewMethodSwitch, \ |
||||
|
resitel_or_org_required |
||||
|
from . import views |
||||
|
|
||||
|
urlpatterns = [ |
||||
|
path('org/add_solution', org_required(views.PosliReseniView.as_view()), name='seminar_vloz_reseni'), |
||||
|
path('resitel/nahraj_reseni', resitel_required(views.NahrajReseniView.as_view()), name='seminar_nahraj_reseni'), |
||||
|
path('resitel/odevzdana_reseni/', resitel_or_org_required(views.PrehledOdevzdanychReseni.as_view()), name='seminar_resitel_odevzdana_reseni'), |
||||
|
|
||||
|
path('org/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'), |
||||
|
path('org/reseni/rocnik/<int:rocnik>/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'), |
||||
|
path('org/reseni/<int:problem>/<int:resitel>/', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'), |
||||
|
path('org/reseni/<int:pk>', org_required(viewMethodSwitch(get=views.DetailReseniView.as_view(), post=views.hodnoceniReseniView)), name='odevzdavatko_detail_reseni'), |
||||
|
path('org/reseni/all', org_required(views.SeznamReseniView.as_view())), |
||||
|
path('org/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())), |
||||
|
] |
@ -0,0 +1,2 @@ |
|||||
|
from .models_all import * |
||||
|
from .odevzdavatko import * |
@ -0,0 +1,188 @@ |
|||||
|
import os |
||||
|
|
||||
|
import reversion |
||||
|
|
||||
|
from django.contrib.sites.shortcuts import get_current_site |
||||
|
from django.db import models |
||||
|
from django.db.models import Sum |
||||
|
from django.urls import reverse_lazy |
||||
|
from django.utils import timezone |
||||
|
from django.conf import settings |
||||
|
|
||||
|
from seminar.models import models_all as am |
||||
|
|
||||
|
|
||||
|
@reversion.register(ignore_duplicates=True) |
||||
|
class Reseni(am.SeminarModelBase): |
||||
|
|
||||
|
class Meta: |
||||
|
db_table = 'seminar_reseni' |
||||
|
verbose_name = 'Řešení' |
||||
|
verbose_name_plural = 'Řešení' |
||||
|
#ordering = ['-problem', 'resitele'] # FIXME: Takhle to chceme, ale nefunguje to. |
||||
|
ordering = ['-cas_doruceni'] |
||||
|
|
||||
|
# Interní ID |
||||
|
id = models.AutoField(primary_key = True) |
||||
|
|
||||
|
# Ke každé dvojici řešní a problém existuje nanejvýš jedno hodnocení, doplnění vazby. |
||||
|
problem = models.ManyToManyField(am.Problem, verbose_name='problém', help_text='Problém', |
||||
|
through='Hodnoceni') |
||||
|
|
||||
|
resitele = models.ManyToManyField(am.Resitel, verbose_name='autoři řešení', |
||||
|
help_text='Seznam autorů řešení', through='Reseni_Resitele') |
||||
|
|
||||
|
|
||||
|
cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True) |
||||
|
|
||||
|
FORMA_PAPIR = 'papir' |
||||
|
FORMA_EMAIL = 'email' |
||||
|
FORMA_UPLOAD = 'upload' |
||||
|
FORMA_CHOICES = [ |
||||
|
(FORMA_PAPIR, 'Papírové řešení'), |
||||
|
(FORMA_EMAIL, 'Emailem'), |
||||
|
(FORMA_UPLOAD, 'Upload přes web'), |
||||
|
] |
||||
|
forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False, |
||||
|
default=FORMA_EMAIL) |
||||
|
|
||||
|
text_cely = models.OneToOneField('ReseniNode', verbose_name='Plná verze textu řešení', |
||||
|
blank=True, null=True, related_name="reseni_cely_set", |
||||
|
on_delete=models.PROTECT) |
||||
|
|
||||
|
poznamka = models.TextField('neveřejná poznámka', blank=True, |
||||
|
help_text='Neveřejná poznámka k řešení (plain text)') |
||||
|
|
||||
|
zverejneno = models.BooleanField('řešení zveřejněno', default=False, |
||||
|
help_text='Udává, zda je řešení zveřejněno') |
||||
|
|
||||
|
def verejne_url(self): |
||||
|
return str(reverse_lazy('odevzdavatko_detail_reseni', args=[self.id])) |
||||
|
|
||||
|
def absolute_url(self): |
||||
|
return "https://" + str(get_current_site(None)) + self.verejne_url() |
||||
|
|
||||
|
# má OneToOneField s: |
||||
|
# Konfera |
||||
|
|
||||
|
# má ForeignKey s: |
||||
|
# Hodnoceni |
||||
|
|
||||
|
def sum_body(self): |
||||
|
return self.hodnoceni_set.all().aggregate(Sum('body'))["body__sum"] |
||||
|
|
||||
|
def __str__(self): |
||||
|
return "{}({}): {}({})".format(self.resitele.first(),len(self.resitele.all()), self.problem.first() ,len(self.problem.all())) |
||||
|
# NOTE: Potenciální DB HOG (bez select_related) |
||||
|
|
||||
|
## Pravdepodobne uz nebude potreba: |
||||
|
# def save(self, *args, **kwargs): |
||||
|
# if ((self.cislo_body is None) and (self.problem.cislo_reseni) and |
||||
|
# (self.problem.typ == Problem.TYP_ULOHA)): |
||||
|
# self.cislo_body = self.problem.cislo_reseni |
||||
|
# super(Reseni, self).save(*args, **kwargs) |
||||
|
|
||||
|
class Hodnoceni(am.SeminarModelBase): |
||||
|
class Meta: |
||||
|
db_table = 'seminar_hodnoceni' |
||||
|
verbose_name = 'Hodnocení' |
||||
|
verbose_name_plural = 'Hodnocení' |
||||
|
|
||||
|
# Interní ID |
||||
|
id = models.AutoField(primary_key = True) |
||||
|
|
||||
|
|
||||
|
body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='body', |
||||
|
blank=True, null=True) |
||||
|
|
||||
|
cislo_body = models.ForeignKey(am.Cislo, verbose_name='číslo pro body', |
||||
|
related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT) |
||||
|
|
||||
|
reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) |
||||
|
|
||||
|
problem = models.ForeignKey(am.Problem, verbose_name='problém', |
||||
|
related_name='hodnoceni', on_delete=models.PROTECT) |
||||
|
|
||||
|
def __str__(self): |
||||
|
return "{}, {}, {}".format(self.problem, self.reseni, self.body) |
||||
|
|
||||
|
def generate_filename(self, filename): |
||||
|
return os.path.join( |
||||
|
settings.SEMINAR_RESENI_DIR, |
||||
|
am.aux_generate_filename(self, filename) |
||||
|
) |
||||
|
|
||||
|
|
||||
|
@reversion.register(ignore_duplicates=True) |
||||
|
class PrilohaReseni(am.SeminarModelBase): |
||||
|
|
||||
|
class Meta: |
||||
|
db_table = 'seminar_priloha_reseni' |
||||
|
verbose_name = 'Příloha řešení' |
||||
|
verbose_name_plural = 'Přílohy řešení' |
||||
|
ordering = ['reseni', 'vytvoreno'] |
||||
|
|
||||
|
# Interní ID |
||||
|
id = models.AutoField(primary_key = True) |
||||
|
|
||||
|
reseni = models.ForeignKey(Reseni, verbose_name='řešení', related_name='prilohy', |
||||
|
on_delete=models.CASCADE) |
||||
|
|
||||
|
vytvoreno = models.DateTimeField('vytvořeno', default=timezone.now, blank=True, editable=False) |
||||
|
|
||||
|
soubor = models.FileField('soubor', upload_to = generate_filename) |
||||
|
|
||||
|
poznamka = models.TextField('neveřejná poznámka', blank=True, |
||||
|
help_text='Neveřejná poznámka k příloze řešení (plain text), např. o původu') |
||||
|
|
||||
|
res_poznamka = models.TextField('poznámka řešitele', blank=True, |
||||
|
help_text='Poznámka k příloze řešení, např. co daný soubor obsahuje') |
||||
|
|
||||
|
def __str__(self): |
||||
|
return str(self.soubor) |
||||
|
|
||||
|
def split(self): |
||||
|
"Vrátí cestu rozsekanou po složkách. To se hodí v templatech" |
||||
|
# Věřím, že tohle funguje, případně použít os.path nebo pathlib. |
||||
|
return self.soubor.url.split('/') |
||||
|
|
||||
|
|
||||
|
# Vazebna tabulka. Mozna se generuje automaticky. |
||||
|
@reversion.register(ignore_duplicates=True) |
||||
|
class Reseni_Resitele(models.Model): |
||||
|
|
||||
|
class Meta: |
||||
|
db_table = 'seminar_reseni_resitele' |
||||
|
verbose_name = 'Řešení řešitelů' |
||||
|
verbose_name_plural = 'Řešení řešitelů' |
||||
|
ordering = ['reseni', 'resitele'] |
||||
|
|
||||
|
# Interní ID |
||||
|
id = models.AutoField(primary_key = True) |
||||
|
|
||||
|
resitele = models.ForeignKey(am.Resitel, verbose_name='řešitel', on_delete=models.PROTECT) |
||||
|
|
||||
|
reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) |
||||
|
|
||||
|
# podil - jakou merou se ktery resitel podilel na danem reseni |
||||
|
# - pouziti v budoucnu, pokud by resitele nemeli dostat vsichni stejne bodu za spolecne reseni |
||||
|
|
||||
|
def __str__(self): |
||||
|
return '{} od {}'.format(self.reseni, self.resitel) |
||||
|
# NOTE: Poteciální DB HOG bez select_related |
||||
|
|
||||
|
class ReseniNode(am.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) |
||||
|
|
@ -1,6 +1,5 @@ |
|||||
from .views_all import * |
from .views_all import * |
||||
from .views_rest import * |
from .views_rest import * |
||||
from .odevzdavatko import * |
|
||||
|
|
||||
# Dočsasné views |
# Dočsasné views |
||||
from .docasne import * |
from .docasne import * |
||||
|
Loading…
Reference in new issue