You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

533 lines
18 KiB

# -*- coding: utf-8 -*-
import os
import datetime
import random
from django.db import models
from django.contrib import auth
from django.utils import timezone
from django.conf import settings
from django.utils.encoding import python_2_unicode_compatible
from django.utils.encoding import force_unicode
from django.utils.text import slugify
from django.core.urlresolvers import reverse
from django_countries.fields import CountryField
from solo.models import SingletonModel
from taggit.managers import TaggableManager
import reversion
from seminar.utils import roman
# TODO společná báze (admin url, url, veřejné, ...)
class SeminarModelBase(models.Model):
class Meta:
abstract = True
def verejne(self):
return False
def public_url(self):
if self.Meta.url_name:
return reverse(self.Meta.url_name,
kwargs={'id': self.id, 'pk': self.id})
return None
def admin_url(self):
model_name = self.__class__.__name__.lower()
return reverse('admin:seminar_%s_change'%(model_name, ), args=(self.id, ))
def verejne_url(self):
return None
#
# Mělo by být částečně vytaženo z Aesopa
# viz https://ovvp.mff.cuni.cz/wiki/aesop/export-skol.
#
@reversion.register(ignore_duplicate_revisions=True)
@python_2_unicode_compatible
class Skola(SeminarModelBase):
class Meta:
db_table = 'seminar_skoly'
verbose_name = u'Škola'
verbose_name_plural = u'Školy'
# Interní ID
id = models.AutoField(primary_key = True)
# Aesopi ID "izo:..." nebo "aesop:..."
# NULL znamená v exportu do aesopa "ufo"
aesop_id = models.CharField(u'Aesop ID', max_length=32, blank=True, default='',
help_text=u'Aesopi ID typu "izo:..." nebo "aesop:..."')
# Staré ID z DAKOSU -- jen u importovaných záznamů
dakos_id = models.CharField(u'Stare DaKoS ID', max_length=32, blank=True, default='',
help_text=u'DaKoS ID z exportu, jen historický význam, podle tabulky dksroot.V_skola')
# IZO školy (jen české školy)
izo = models.CharField(u'IZO', max_length=32, blank=True,
help_text=u'IZO školy (jen české školy)')
# Celý název školy
nazev = models.CharField(u'název', max_length=256,
help_text=u'Celý název školy')
# Zkraceny nazev pro zobrazení ve výsledkovce, volitelné.
# Není v Aesopovi, musíme vytvářet sami.
kratky_nazev = models.CharField(u'zkrácený název', max_length=256, blank=True,
help_text="Zkrácený název pro zobrazení ve výsledkovce")
# Ulice může být jen číslo
ulice = models.CharField(u'ulice', max_length=256)
mesto = models.CharField(u'město', max_length=256)
psc = models.CharField(u'PSČ', max_length=32)
# ISO 3166-1 dvojznakovy kod zeme velkym pismem (CZ, SK)
# Ekvivalentní s CharField(max_length=2, default='CZ', ...)
stat = CountryField(u'stát', default='CZ',
help_text=u'ISO 3166-1 kód zeme velkými písmeny (CZ, SK, ...)')
# Jaké vzdělání škpla poskytuje?
je_zs = models.BooleanField(u'základní stupeň', default=True)
je_ss = models.BooleanField(u'střední stupeň', default=True)
poznamka = models.TextField(u'neveřejná poznámka', blank=True,
help_text=u'Neveřejná poznámka ke škole (plain text)')
def __str__(self):
return force_unicode(u'%s, %s' % (self.nazev, self.mesto))
@reversion.register(ignore_duplicate_revisions=True)
@python_2_unicode_compatible
class Resitel(SeminarModelBase):
class Meta:
db_table = 'seminar_resitele'
verbose_name = u'Řešitel'
verbose_name_plural = u'Řešitelé'
ordering = ['prijmeni', 'jmeno']
# Interní ID
id = models.AutoField(primary_key = True)
jmeno = models.CharField(u'jméno', max_length=256)
prijmeni = models.CharField(u'příjmení', max_length=256)
# User, pokud má na webu účet
user = models.OneToOneField(settings.AUTH_USER_MODEL, blank=True, null=True, verbose_name=u'uživatel')
# Pohlaví. Že ho neznáme se snad nestane (a ušetří to práci při programování)
pohlavi_muz = models.BooleanField(u'pohlaví (muž)', default=False)
skola = models.ForeignKey(Skola, blank=True, null=True, verbose_name=u'škola')
# Očekávaný rok maturity a vyřazení z aktivních řešitelů
rok_maturity = models.IntegerField(u'rok maturity')
email = models.EmailField(u'e-mail', max_length=256, blank=True, default='')
telefon = models.CharField(u'telefon', max_length=256, blank=True, default='')
datum_narozeni = models.DateField(u'datum narození', blank=True, null=True)
# NULL dokud nedali souhlas
datum_souhlasu_udaje = models.DateField(u'datum souhlasu (údaje)', blank=True, null=True,
help_text=u'Datum souhlasu se zpracováním osobních údajů')
# NULL dokud nedali souhlas
datum_souhlasu_zasilani = models.DateField(u'datum souhlasu (spam)', blank=True, null=True,
help_text=u'Datum souhlasu se zasíláním MFF materiálů')
# Alespoň odhad (rok či i měsíc)
datum_prihlaseni = models.DateField(u'datum přihlášení', default=timezone.now)
ZASILAT_DOMU = 'domu'
ZASILAT_DO_SKOLY = 'do_skoly'
ZASILAT_NIKAM = 'nikam'
ZASILAT_CHOICES = [
(ZASILAT_DOMU, u'Domů'),
(ZASILAT_DO_SKOLY, u'Do školy'),
(ZASILAT_NIKAM, u'Nikam'),
]
zasilat = models.CharField(u'kam zasílat', max_length=32, choices=ZASILAT_CHOICES, blank=False, default=ZASILAT_DOMU)
# Ulice může být i jen číslo
ulice = models.CharField(u'ulice', max_length=256, blank=True, default='')
mesto = models.CharField(u'město', max_length=256, blank=True, default='')
psc = models.CharField(u'PSČ', max_length=32, blank=True, default='')
# ISO 3166-1 dvojznakovy kod zeme velkym pismem (CZ, SK)
# Ekvivalentní s CharField(max_length=2, default='CZ', ...)
stat = CountryField(u'stát', default='CZ',
help_text=u'ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)')
poznamka = models.TextField(u'neveřejná poznámka', blank=True,
help_text=u'Neveřejná poznámka k řešiteli (plain text)')
# Staré ID z DAKOSU -- jen u importovaných záznamů
dakos_id = models.CharField(u'Stare DaKoS ID', max_length=32, blank=True, default='',
help_text=u'DaKoS ID z exportu, jen historický význam, podle tabulky mamoper.MM_RIESITELIA')
def plne_jmeno(self):
return force_unicode(u'%s %s' % (self.jmeno, self.prijmeni))
def __str__(self):
return force_unicode(self.plne_jmeno())
@reversion.register(ignore_duplicate_revisions=True)
@python_2_unicode_compatible
class Rocnik(SeminarModelBase):
class Meta:
db_table = 'seminar_rocniky'
verbose_name = u'Ročník'
verbose_name_plural = u'Ročníky'
ordering = ['rocnik']
# Interní ID
id = models.AutoField(primary_key = True)
prvni_rok = models.IntegerField(u'první rok')
rocnik = models.CharField(u'číslo ročníku', max_length=16)
def __str__(self):
return force_unicode(u'%s (%d/%d)' % (self.rocnik, self.prvni_rok, self.prvni_rok+1))
def roman(self):
if self.rocnik.isdigit():
return force_unicode(roman(int(self.rocnik)))
else:
return force_unicode(self.rocnik)
def verejne(self):
return len(self.verejna_cisla()) > 0
verejne.boolean = True
def verejna_cisla(self):
vc = [c for c in self.cisla.all() if c.verejne()]
vc.sort(key=lambda c: c.cislo)
return vc
def posledni_verejne_cislo(self):
vc = self.verejna_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={'pk': self.id})
@reversion.register(ignore_duplicate_revisions=True)
@python_2_unicode_compatible
class Cislo(SeminarModelBase):
class Meta:
db_table = 'seminar_cisla'
verbose_name = u'Číslo'
verbose_name_plural = u'Čísla'
ordering = ['rocnik__rocnik', 'cislo']
# Interní ID
id = models.AutoField(primary_key = True)
rocnik = models.ForeignKey(Rocnik, verbose_name=u'ročník', related_name='cisla')
cislo = models.CharField(u'název čísla', max_length=32,
help_text=u'Většinou jen "1", vyjímečně "7-8", lexikograficky určije pořadí v ročníku!')
datum_vydani = models.DateField(u'datum vydání', blank=True, null=True,
help_text=u'Datum vydání finální verze')
datum_deadline = models.DateField(u'datum deadline', blank=True, null=True,
help_text=u'Datum pro příjem řešení úloh zadaných v tomto čísle')
verejne_db = models.BooleanField(u'číslo zveřejněno', db_column='verejne', default=False)
def kod(self):
return u'%s.%s' % (self.rocnik.rocnik, self.cislo)
kod.short_description = u'Kód čísla'
def __str__(self):
return force_unicode(u'%s' % (self.kod(),))
def verejne(self):
return self.verejne_db
verejne.boolean = True
def verejne_url(self):
return reverse('seminar_cislo', kwargs={'pk': self.id})
@reversion.register(ignore_duplicate_revisions=True)
@python_2_unicode_compatible
class Problem(SeminarModelBase):
class Meta:
db_table = 'seminar_problemy'
verbose_name = u'Problém'
verbose_name_plural = u'Problémy'
# Interní ID
id = models.AutoField(primary_key = True)
# Název
nazev = models.CharField(u'název', max_length=256)
TYP_ULOHA = 'uloha'
TYP_TEMA = 'tema'
TYP_SERIAL = 'serial'
TYP_ORG_CLANEK = 'org-clanek'
TYP_RES_CLANEK = 'res-clanek'
TYP_CHOICES = [
(TYP_ULOHA, u'Úloha'),
(TYP_TEMA, u'Téma'),
(TYP_SERIAL, u'Seriál'),
(TYP_ORG_CLANEK, u'Organizátorský článek'),
(TYP_RES_CLANEK, u'Řesitelský článek'),
]
typ = models.CharField(u'typ problému', max_length=32, choices=TYP_CHOICES, blank=False, default=TYP_ULOHA)
STAV_NAVRH = 'navrh'
STAV_ZADANY = 'zadany'
STAV_SMAZANY = 'smazany'
STAV_CHOICES = [
(STAV_NAVRH, u'Návrh'),
(STAV_ZADANY, u'Zadaný'),
(STAV_SMAZANY, u'Smazaný'),
]
stav = models.CharField(u'stav problému', max_length=32, choices=STAV_CHOICES, blank=False, default=STAV_NAVRH)
zamereni = TaggableManager(verbose_name=u'zaměření', help_text='Zaměření M/F/I/O problému, příp. další tagy', blank=True)
text_problemu_org = models.TextField(u'organizátorský (neveřejný) text', blank=True)
text_problemu = models.TextField(u'veřejný text zadání a řešení', blank=True)
autor = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'autor problému', related_name='autor_uloh', null=True, blank=True)
opravovatel = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'opravovatel', null=True, blank=True,
related_name='opravovatel_uloh')
kod = models.CharField(u'lokální kód', max_length=32, blank=True, default='',
help_text=u'Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku')
cislo_zadani = models.ForeignKey(Cislo, verbose_name=u'číslo zadání', blank=True, null=True, related_name=u'zadane_problemy')
cislo_reseni = models.ForeignKey(Cislo, verbose_name=u'číslo řešení', blank=True, null=True, related_name=u'resene_problemy',
help_text=u'Číslo s řešením úlohy, jen pro úlohy')
body = models.IntegerField(u'maximum bodů', blank=True, null=True)
timestamp = models.DateTimeField(u'vytvořeno', auto_now=True)
# Staré ID z DAKOSU -- jen u importovaných záznamů
dakos_id = models.CharField(u'Staré DaKoS ID', max_length=32, blank=True, default='',
help_text=(u'DaKoS ID z exportu, s prefixem podle původu: "AZAD:xxx (z MM_AZAD), "' +
u'"DOZ:xxx" (z MM_DOZ), "ZAD:rocnik.cislo.uloha.typ" (z MM_ZADANIA)'))
def __str__(self):
return force_unicode(u'%s (%s)' % (self.nazev, self.stav))
def kod_v_rocniku(self):
if self.typ == self.TYP_ULOHA:
return force_unicode(u"%s.u%s" % (self.cislo_zadani.cislo, self.kod,))
if self.typ == self.TYP_TEMA:
return force_unicode(u"t%s" % (self.kod,))
return ''
def verejne(self):
return (self.cislo_zadani and self.cislo_zadani.verejne())
verejne.boolean = True
def verejne_url(self):
return reverse('seminar_problem', kwargs={'pk': self.id})
@reversion.register(ignore_duplicate_revisions=True)
@python_2_unicode_compatible
class Reseni(SeminarModelBase):
class Meta:
db_table = 'seminar_reseni'
verbose_name = u'Řešení'
verbose_name_plural = u'Řešení'
ordering = ['problem', 'resitel']
# Interní ID
id = models.AutoField(primary_key = True)
problem = models.ForeignKey(Problem, verbose_name=u'problém', related_name='reseni')
resitel = models.ForeignKey(Resitel, verbose_name=u'řešitel', related_name='reseni')
body = models.IntegerField(u'body', blank=True, null=True)
cislo_body = models.ForeignKey(Cislo, verbose_name=u'číslo pro body', related_name='bodovana_reseni', blank=True, null=True)
timestamp = models.DateTimeField(u'vytvořeno', auto_now=True)
poznamka = models.TextField(u'neveřejná poznámka', blank=True,
help_text=u'Neveřejná poznámka k řešení (plain text, editace v detailu řešení)')
def __str__(self):
return force_unicode(u"%s: %s (%sb)" % (self.resitel.plne_jmeno(), self.problem.nazev, self.body))
# PrilohaReseni method
def generate_filename(self, filename):
clean = filename.replace('/','-').replace('\0', '')
datedir = datetime.datetime.now().strftime('%Y-%m')
fname = "%s_%s" % (
datetime.datetime.now().strftime('%Y-%m-%d-%H:%M'),
clean)
return os.path.join(settings.SEMINAR_RESENI_DIRNAME, datedir, fname)
@reversion.register(ignore_duplicate_revisions=True)
@python_2_unicode_compatible
class PrilohaReseni(SeminarModelBase):
class Meta:
db_table = 'seminar_priloha_reseni'
verbose_name = u'Příloha řešení'
verbose_name_plural = u'Přílohy řešení'
ordering = ['reseni', 'timestamp']
# Interní ID
id = models.AutoField(primary_key = True)
reseni = models.ForeignKey(Reseni, verbose_name=u'řešení', related_name='prilohy')
timestamp = models.DateTimeField(u'vytvořeno', auto_now=True)
soubor = models.FileField(u'soubor', upload_to = generate_filename)
poznamka = models.TextField(u'neveřejná poznámka', blank=True,
help_text=u'Neveřejná poznámka k příloze řešení (plain text), např. o původu')
def __str__(self):
return force_unicode(self.soubor)
@reversion.register(ignore_duplicate_revisions=True)
@python_2_unicode_compatible
class Soustredeni(SeminarModelBase):
class Meta:
db_table = 'seminar_soustredeni'
verbose_name = u'Soustředění'
verbose_name_plural = u'Soustředění'
ordering = ['rocnik__rocnik', 'datum_zacatku']
# Interní ID
id = models.AutoField(primary_key = True)
rocnik = models.ForeignKey(Rocnik, verbose_name=u'ročník', related_name='soustredeni')
datum_zacatku = models.DateField(u'datum začátku', blank=True, null=True,
help_text=u'První den soustředění')
datum_konce = models.DateField(u'datum konce', blank=True, null=True,
help_text=u'Poslední den soustředění')
verejne_db = models.BooleanField(u'soustředění zveřejněno', db_column='verejne', default=False)
misto = models.CharField(u'místo soustředění', max_length=256, blank=True, default='',
help_text=u'Místo (název obce, volitelně též objektu')
ucastnici = models.ManyToManyField(Resitel, verbose_name=u'účastníci soustředění',
help_text=u'Seznam účastníků soustředění', db_table='seminar_soustredeni_ucastnici')
def __str__(self):
return force_unicode(u'%s (%s)' % (self.misto, self.datum_zacatek))
def verejne(self):
return self.verejne_db
verejne.boolean = True
def verejne_url(self):
return reverse('seminar_soustredeni', kwargs={'pk': self.id})
@python_2_unicode_compatible
class VysledkyBase(SeminarModelBase):
class Meta:
verbose_name = u'Řádek výsledkovky'
verbose_name_plural = u'Řádky výsledkovky'
ordering = ['body']
abstract = True
managed = False
dummy_id = models.CharField(u'dummy ID pro view', max_length=32, primary_key=True, db_column='id')
cislo = models.ForeignKey(Cislo, verbose_name=u'číslo pro body', db_column='cislo_id', on_delete=models.DO_NOTHING)
resitel = models.ForeignKey(Resitel, verbose_name=u'řešitel', db_column='resitel_id', on_delete=models.DO_NOTHING)
body = models.IntegerField(u'body za číslo', db_column='body')
def __str__(self):
return force_unicode(u"%s: %sb (%s)" % (self.resitel.plne_jmeno(), self.body, str(self.cislo)))
@python_2_unicode_compatible
class VysledkyZaCislo(VysledkyBase):
class Meta:
db_table = 'seminar_body_za_cislo'
abstract = False
managed = False
def __str__(self):
return force_unicode(u"%s: %sb (za %s)" % (self.resitel.plne_jmeno(), self.body, str(self.cislo)))
@python_2_unicode_compatible
class VysledkyKCislu(VysledkyBase):
class Meta:
db_table = 'seminar_body_k_cislu'
abstract = False
managed = False
body_celkem = models.IntegerField(u'body celkem do čísla', db_column='body_celkem')
def __str__(self):
return force_unicode(u"%s: %sb / %sb (do %s)" % (self.resitel.plne_jmeno(), self.body, self.body_celkem, str(self.cislo)))
@reversion.register(ignore_duplicate_revisions=True)
@python_2_unicode_compatible
class Nastaveni(SingletonModel):
class Meta:
db_table = 'seminar_nastaveni'
verbose_name = u'Nastavení semináře'
aktualni_rocnik = models.ForeignKey(Rocnik, verbose_name=u'aktuální ročník', null=False)
aktualni_cislo = models.ForeignKey(Cislo, verbose_name=u'poslední vydané číslo', null=False)
def __str__(self):
return u'Nastavení semináře'
def admin_url(self):
return reverse('admin:seminar_nastaveni_change', args=(self.id, ))
def verejne(self):
return False