Compare commits
10 commits
acd220a0b5
...
0472f0ee90
| Author | SHA1 | Date | |
|---|---|---|---|
| 0472f0ee90 | |||
| 16cf18661a | |||
| 0fa3a2a077 | |||
| 0c8875e6e4 | |||
| bc585679dc | |||
| 0747ede60a | |||
| 3e30fe188f | |||
| ee6e8ecef5 | |||
| 58b8bccd02 | |||
| d6b722bc24 |
52 changed files with 1007 additions and 1033 deletions
|
|
@ -1,9 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují:
|
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují.
|
||||||
|
|
||||||
- ``aesop-export/mam-rocnik-<int:prvni_rok>.csv`` (seminar_export_rocnik) :class:`~aesop.views.ExportRocnikView`
|
|
||||||
- ``aesop-export/mam-sous-<str:datum_zacatku>.csv`` (seminar_export_sous) :class:`~aesop.views.ExportSousView`
|
|
||||||
- ``aesop-export/index.csv`` (seminar_export_index) :class:`~aesop.views.ExportIndexView`
|
|
||||||
"""
|
"""
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from aesop import views
|
from aesop import views
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
import seminar.models as m
|
import seminar.models as m
|
||||||
import seminar.views as v
|
import seminar.views as v
|
||||||
from seminar.utils import sync_skoly
|
from personalni.utils import sync_skoly
|
||||||
|
|
||||||
class OrgSkolyAutocompleteTestCase(TestCase):
|
class OrgSkolyAutocompleteTestCase(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
||||||
11
api/urls.py
11
api/urls.py
|
|
@ -1,14 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují:
|
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují.
|
||||||
|
|
||||||
- ``api/expor/skoly/`` (export_skoly) :func:`~api.views.exports.exportSkolView`
|
|
||||||
- ``api/autocomplete/skola/`` (autocomplete_skola) :class:`~api.views.autocomplete.SkolaAutocomplete`
|
|
||||||
- ``api/autocomplete/resitel/`` (autocomplete_resitel) :class:`~api.views.autocomplete.ResitelAutocomplete`
|
|
||||||
- ``api/autocomplete/problem/odevzdatelny`` (autocomplete_problem_odevzdatelny) :class:`~api.views.autocomplete.OdevzdatelnyProblemAutocomplete`
|
|
||||||
|
|
||||||
Na autocomplete v3 čeká:
|
|
||||||
|
|
||||||
- ``autocomplete/organizatori/`` (seminar_autocomplete_organizator) :class:`~api.views.autocomplete.OrganizatorAutocomplete`
|
|
||||||
"""
|
"""
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují:
|
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují.
|
||||||
|
|
||||||
- ``<int:pk>/`` :func:`~galerie.views.nahled`
|
|
||||||
- ``<int:pk>/<int:fotka>/`` :func:`~galerie.views.detail`
|
|
||||||
- ``<int:galerie>/new/`` :func:`~galerie.views.new_galerie`
|
|
||||||
- ``<int:galerie>/plus/<int:subgalerie>/`` :func:`~galerie.views.plus_galerie`
|
|
||||||
- ``<int:galerie>/minus/<int:subgalerie>/`` :func:`~galerie.views.minus_galerie`
|
|
||||||
"""
|
"""
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from personalni.utils import org_required
|
from personalni.utils import org_required
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,6 @@ Soubor sloužící k definici toho, co bude v adminu. Většinou pouhým zavolá
|
||||||
funkce :func:`django.contrib.admin.site.register`, v případě, že chceme něco
|
funkce :func:`django.contrib.admin.site.register`, v případě, že chceme něco
|
||||||
upravit, tak jako třída rozšiřující :class:`django.contrib.admin.ModelAdmin`
|
upravit, tak jako třída rozšiřující :class:`django.contrib.admin.ModelAdmin`
|
||||||
s dekorátorem :func:`django.contrib.admin.register`.
|
s dekorátorem :func:`django.contrib.admin.register`.
|
||||||
|
|
||||||
Zde se definuje admin pro:
|
|
||||||
|
|
||||||
- :class:`~header_fotky.models.FotkaHeader`
|
|
||||||
- :class:`~header_fotky.models.FotkaUrlVazba`
|
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.admin import ModelAdmin
|
from django.contrib.admin import ModelAdmin
|
||||||
|
|
@ -22,4 +17,4 @@ class FotkaPozadiAdmin(ModelAdmin):
|
||||||
readonly_fields = ['cas']
|
readonly_fields = ['cas']
|
||||||
|
|
||||||
admin.site.register(m.FotkaHeader, FotkaPozadiAdmin)
|
admin.site.register(m.FotkaHeader, FotkaPozadiAdmin)
|
||||||
admin.site.register(m.FotkaUrlVazba)
|
admin.site.register(m.FotkaUrlVazba)
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,6 @@ Soubor sloužící k definici toho, co bude v adminu. Většinou pouhým zavolá
|
||||||
funkce :func:`django.contrib.admin.site.register`, v případě, že chceme něco
|
funkce :func:`django.contrib.admin.site.register`, v případě, že chceme něco
|
||||||
upravit, tak jako třída rozšiřující :class:`django.contrib.admin.ModelAdmin`
|
upravit, tak jako třída rozšiřující :class:`django.contrib.admin.ModelAdmin`
|
||||||
s dekorátorem :func:`django.contrib.admin.register`.
|
s dekorátorem :func:`django.contrib.admin.register`.
|
||||||
|
|
||||||
Zde se definuje admin pro:
|
|
||||||
|
|
||||||
- :class:`korektury.models.KorekturovanePDF`
|
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from reversion.admin import VersionAdmin
|
from reversion.admin import VersionAdmin
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují:
|
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují.
|
||||||
|
|
||||||
- ``korektury/`` (korektury_list) :class:`~korektury.views.KorekturySeskupeneListView`
|
|
||||||
- ``korektury/neseskupene/`` (korektury_neseskupene_list) :class:`~korektury.views.KorekturyAktualniListView`
|
|
||||||
- ``korektury/zastarale/`` (korektury_stare_list) :class:`~korektury.views.KorekturyZastaraleListView`
|
|
||||||
- ``korektury/<int:pdf>/`` (korektury) :class:`~korektury.views.KorekturyView`
|
|
||||||
"""
|
"""
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from personalni.utils import org_required
|
from personalni.utils import org_required
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Soubor sloužící jako základní „router“, tj. zde se includují veškeré ostatní urls:
|
Soubor sloužící jako základní „router“, tj. zde se includují veškeré ostatní urls.
|
||||||
|
|
||||||
- ``admin/`` :mod:`django.contrib.admin.site.urls`
|
|
||||||
- ``ckeditor/`` :mod:`ckeditor_uploader.urls`
|
|
||||||
- :mod:`seminar.urls`
|
|
||||||
- :mod:`odevzdavatko.urls`
|
|
||||||
- :mod:`korektury.urls`
|
|
||||||
- :mod:`prednasky.urls`
|
|
||||||
- :mod:`soustredeni.urls`
|
|
||||||
- :mod:`personalni.urls`
|
|
||||||
- :mod:`various.autentizace.urls`
|
|
||||||
- :mod:`api.urls`
|
|
||||||
- :mod:`treenode.urls`
|
|
||||||
- :mod:`aesop.urls`
|
|
||||||
- ``comments_dj/`` :mod:`django_comments.urls`
|
|
||||||
"""
|
"""
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||||
|
|
@ -34,6 +20,9 @@ urlpatterns = [
|
||||||
# Seminarova aplikace (ma vlastni podadresare)
|
# Seminarova aplikace (ma vlastni podadresare)
|
||||||
path('', include('seminar.urls')),
|
path('', include('seminar.urls')),
|
||||||
|
|
||||||
|
# Obsahová plikace (ma vlastni podadresare)
|
||||||
|
path('', include('tvorba.urls')),
|
||||||
|
|
||||||
# Odevzdavatko (ma vlastni podadresare)
|
# Odevzdavatko (ma vlastni podadresare)
|
||||||
path('', include('odevzdavatko.urls')),
|
path('', include('odevzdavatko.urls')),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují:
|
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují.
|
||||||
|
|
||||||
- ``org/add_solution`` (seminar_vloz_reseni) :class:`~odevzdavatko.views.PosliReseniView`
|
|
||||||
- ``resitel/nahraj_reseni`` (seminar_nahraj_reseni) :class:`~odevzdavatko.views.NahrajReseniView`
|
|
||||||
- ``resitel/odevzdana_reseni/`` (seminar_resitel_odevzdana_reseni) :class:`~odevzdavatko.views.PrehledOdevzdanychReseni`
|
|
||||||
- ``org/reseni/`` (odevzdavatko_tabulka) :class:`~odevzdavatko.views.TabulkaOdevzdanychReseniView`
|
|
||||||
- ``org/reseni/rocnik/<int:rocnik>/`` (odevzdavatko_tabulka) :class:`~odevzdavatko.views.TabulkaOdevzdanychReseniView`
|
|
||||||
- ``org/reseni/<int:problem>/<int:resitel>/`` (odevzdavatko_reseni_resitele_k_problemu) :class:`~odevzdavatko.views.ReseniProblemuView`
|
|
||||||
- ``org/reseni/<int:pk>/`` (odevzdavatko_detail_reseni) :func:`~seminar.utils.viewMethodSwitch` + :class:`~odevzdavatko.views.DetailReseniView` + :func:`~odevzdavatko.views.hodnoceniReseniView`
|
|
||||||
- ``org/reseni/all`` :class:`~odevzdavatko.views.SeznamReseniView`
|
|
||||||
- ``org/reseni/akt`` :class:`~odevzdavatko.views.TabulkaOdevzdanychReseniView`
|
|
||||||
- ``resitel/reseni/<int:pk>`` (odevzdavatko_resitel_reseni) :class:`~odevzdavatko.views.ResitelReseniView`
|
|
||||||
"""
|
"""
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,23 @@ from mamweb.models.base import SeminarModelBase
|
||||||
from .osoba import Osoba
|
from .osoba import Osoba
|
||||||
|
|
||||||
|
|
||||||
|
class _OrganizatorManager(models.Manager):
|
||||||
|
def aktivni(self, datum=timezone.now()):
|
||||||
|
return self.exclude(
|
||||||
|
organizuje_do__isnull=False,
|
||||||
|
organizuje_do__lt=datum
|
||||||
|
).order_by('osoba__jmeno')
|
||||||
|
|
||||||
|
def neaktivni(self, datum=timezone.now()):
|
||||||
|
return self.exclude(
|
||||||
|
id__in=self.aktivni(datum)
|
||||||
|
).order_by('-organizuje_do')
|
||||||
|
|
||||||
|
|
||||||
@reversion.register(ignore_duplicates=True)
|
@reversion.register(ignore_duplicates=True)
|
||||||
class Organizator(SeminarModelBase):
|
class Organizator(SeminarModelBase):
|
||||||
|
objects = _OrganizatorManager()
|
||||||
|
|
||||||
osoba = models.OneToOneField(
|
osoba = models.OneToOneField(
|
||||||
Osoba, verbose_name='osoba', related_name='org',
|
Osoba, verbose_name='osoba', related_name='org',
|
||||||
help_text='osobní údaje organizátora', null=False, blank=False,
|
help_text='osobní údaje organizátora', null=False, blank=False,
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují:
|
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují.
|
||||||
|
|
||||||
- ``org/add_solution`` (seminar_vloz_reseni) :class:`~odevzdavatko.views.PosliReseniView`
|
|
||||||
- ``resitel/nahraj_reseni`` (seminar_nahraj_reseni) :class:`~odevzdavatko.views.NahrajReseniView`
|
|
||||||
- ``resitel/odevzdana_reseni/`` (seminar_resitel_odevzdana_reseni) :class:`~odevzdavatko.views.PrehledOdevzdanychReseni`
|
|
||||||
- ``org/reseni/`` (odevzdavatko_tabulka) :class:`~odevzdavatko.views.TabulkaOdevzdanychReseniView`
|
|
||||||
- ``org/reseni/rocnik/<int:rocnik>/`` (odevzdavatko_tabulka) :class:`~odevzdavatko.views.TabulkaOdevzdanychReseniView`
|
|
||||||
- ``org/reseni/<int:problem>/<int:resitel>/`` (odevzdavatko_reseni_resitele_k_problemu) :class:`~odevzdavatko.views.ReseniProblemuView`
|
|
||||||
- ``org/reseni/<int:pk>/`` (odevzdavatko_detail_reseni) :func:`~seminar.utils.viewMethodSwitch` + :class:`~odevzdavatko.views.DetailReseniView` + :func:`~odevzdavatko.views.hodnoceniReseniView`
|
|
||||||
- ``org/reseni/all`` :class:`~odevzdavatko.views.SeznamReseniView`
|
|
||||||
- ``org/reseni/akt`` :class:`~odevzdavatko.views.TabulkaOdevzdanychReseniView`
|
|
||||||
- ``resitel/reseni/<int:pk>`` (odevzdavatko_resitel_reseni) :class:`~odevzdavatko.views.ResitelReseniView`
|
|
||||||
"""
|
"""
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,11 @@ import re
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.decorators import permission_required, user_passes_test
|
from django.contrib.auth.decorators import permission_required, user_passes_test
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from odevzdavatko.models.reseni_resitele import Reseni_Resitele
|
||||||
|
from soustredeni.models.soustredeni_ucastnici import Soustredeni_Ucastnici
|
||||||
|
from soustredeni.models.konfery_ucastnici import Konfery_Ucastnici
|
||||||
import personalni.models as m
|
import personalni.models as m
|
||||||
from various.utils import bez_diakritiky_translate
|
from various.utils import bez_diakritiky_translate
|
||||||
|
|
||||||
|
|
@ -38,3 +42,149 @@ def normalizuj_jmeno(o: m.Osoba) -> str:
|
||||||
cele_jmeno = re.sub(r'[^a-zA-Z- ]', '', cele_jmeno)
|
cele_jmeno = re.sub(r'[^a-zA-Z- ]', '', cele_jmeno)
|
||||||
return cele_jmeno
|
return cele_jmeno
|
||||||
|
|
||||||
|
|
||||||
|
def sync_skoly(base_url):
|
||||||
|
"""Stáhne všechny školy z mamwebu na adrese <base_url> a uloží je do databáze"""
|
||||||
|
from django.urls import reverse
|
||||||
|
full_url = base_url.rstrip('/') + reverse('export_skoly')
|
||||||
|
import requests
|
||||||
|
from django.core import serializers
|
||||||
|
json = requests.get(full_url, stream=True).content
|
||||||
|
for skola in serializers.deserialize('json', json):
|
||||||
|
skola.save()
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def merge_resitele(cilovy, zdrojovy):
|
||||||
|
"""Spojí dva řešitelské objekty do cílového.
|
||||||
|
|
||||||
|
Pojmenování "zdrojový" je silně nepřiléhající, ale co už…"""
|
||||||
|
|
||||||
|
# Postup:
|
||||||
|
# Sjednotit / upravit informace cílového řešitele
|
||||||
|
print('Upravuji data modelu')
|
||||||
|
fieldy_shoda = ['skola', 'rok_maturity', 'zasilat', 'zasilat_cislo_emailem', 'zasilat_cislo_papirove']
|
||||||
|
|
||||||
|
for f in fieldy_shoda:
|
||||||
|
zf = getattr(zdrojovy, f)
|
||||||
|
cf = getattr(cilovy, f)
|
||||||
|
if cf == zf:
|
||||||
|
print(f' Údaj {f} je shodný ({zf})')
|
||||||
|
else:
|
||||||
|
if zf is None:
|
||||||
|
print(f' Údaj {f} je pouze v cílovém, používám')
|
||||||
|
continue
|
||||||
|
if cf is None:
|
||||||
|
setattr(cilovy, f, zf)
|
||||||
|
cilovy.poznamka += f'\nDEBUG: Merge: doplnéný údaj {f} ze zdrojového: {zf}'
|
||||||
|
print(f" Přiřazuji {f} ze zdrojového: {zf}")
|
||||||
|
continue
|
||||||
|
# Jsou fakt různé…
|
||||||
|
# FIXME: chybí možnost na vlastní úpravu…
|
||||||
|
verdikt = input(f"\n\n Údaj {f} se u řešitele {cilovy} ({cilovy.id}) liší:\n Zdrojový: {zf}\n Cílový: {cf}\n Který použít, [z]drojový, [c]ílový? ")
|
||||||
|
verdikt = verdikt[0].casefold()
|
||||||
|
if verdikt == 'z':
|
||||||
|
setattr(cilovy, f, zf)
|
||||||
|
cilovy.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {zf} (zdrojový), nepoužit {cf} (cílový)'
|
||||||
|
elif verdikt == 'c':
|
||||||
|
cilovy.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {cf} (cílový), nepoužit {zf} (zdrojový)'
|
||||||
|
else: raise ValueError('Špatná odpověď, řešitel pravděpodobně neuložen')
|
||||||
|
# poznámku chceme nezahodit…
|
||||||
|
cilovy.poznamka += f'\nDEBUG: Merge: Původní poznámka: {zdrojovy.poznamka}'
|
||||||
|
print(f' Výsledný řešitel: {cilovy.__dict__}, ukládám')
|
||||||
|
cilovy.save()
|
||||||
|
|
||||||
|
|
||||||
|
# Přepojit všechny vazby ze zdrojového na cílového
|
||||||
|
print('Přepojuji vazby')
|
||||||
|
# Vazby: Škola (hotovo), Řešení_Řešitelé, Konfery_Účastníci, Soustředění_Účastníci, Osoba (vyřeší se později, nejde přepojit)
|
||||||
|
ct = Reseni_Resitele.objects.filter(resitele=zdrojovy).update(resitele=cilovy)
|
||||||
|
print(f' Přepojeno {ct} řešení')
|
||||||
|
ct = Konfery_Ucastnici.objects.filter(resitel=zdrojovy).update(resitel=cilovy)
|
||||||
|
print(f' Přepojeno {ct} konfer')
|
||||||
|
ct = Soustredeni_Ucastnici.objects.filter(resitel=zdrojovy).update(resitel=cilovy)
|
||||||
|
print(f' Přepojeno {ct} sousů')
|
||||||
|
|
||||||
|
# Teď by na zdrojovém řešiteli nemělo nic viset, smazat ho, pamatujíce si jeho Osobu
|
||||||
|
zdrosoba = zdrojovy.osoba
|
||||||
|
print(f'Mažu zdrojového řešitele {zdrojovy.__dict__}')
|
||||||
|
zdrojovy.delete()
|
||||||
|
# Spojit osoby (separátní funkce).
|
||||||
|
merge_osoby(cilovy.osoba, zdrosoba)
|
||||||
|
|
||||||
|
input("Potvrdit transakci řešitelů (^C pro zrušení) ")
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def merge_osoby(cilova, zdrojova):
|
||||||
|
""" Spojí dvě osoby do cílové
|
||||||
|
|
||||||
|
Nehlídá omezení typu "max 1 řešitel na osobu", to by měla hlídat databáze (OneToOneField)."""
|
||||||
|
# Sjednocení dat
|
||||||
|
print('Sjednocuji data osob')
|
||||||
|
# ID, User neřešíme, poznámku vyřešíme separátně.
|
||||||
|
fieldy = [
|
||||||
|
'datum_narozeni', 'datum_registrace', 'datum_souhlasu_udaje',
|
||||||
|
'datum_souhlasu_zasilani', 'email', 'foto', 'jmeno', 'mesto',
|
||||||
|
'pohlavi_muz', 'prezdivka', 'prijmeni', 'psc', 'stat', 'telefon', 'ulice'
|
||||||
|
]
|
||||||
|
for f in fieldy:
|
||||||
|
zf = getattr(zdrojova, f)
|
||||||
|
cf = getattr(cilova, f)
|
||||||
|
if cf == zf:
|
||||||
|
print(f' Údaj {f} je shodný ({zf})')
|
||||||
|
else:
|
||||||
|
if zf is None:
|
||||||
|
print(f' Údaj {f} je pouze v cílové, používám')
|
||||||
|
continue
|
||||||
|
if cf is None:
|
||||||
|
setattr(cilova, f, zf)
|
||||||
|
cilova.poznamka += f'\nDEBUG: Merge: doplnéný údaj {f} ze zdrojové: {zf}'
|
||||||
|
print(f" Přiřazuji {f} ze zdrojové: {zf}")
|
||||||
|
continue
|
||||||
|
# Jsou fakt různé…
|
||||||
|
# FIXME: chybí možnost na vlastní úpravu…
|
||||||
|
verdikt = input(f"\n\n Údaj {f} se u osoby {cilova} ({cilova.id}) liší:\n Zdrojový: {zf}\n Cílový: {cf}\n Který použít, [z]drojový, [c]ílový? ")
|
||||||
|
verdikt = verdikt[0].casefold()
|
||||||
|
if verdikt == 'z':
|
||||||
|
setattr(cilova, f, zf)
|
||||||
|
cilova.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {zf} (zdrojová), nepoužit {cf} (cílová)'
|
||||||
|
elif verdikt == 'c':
|
||||||
|
cilova.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {cf} (cílová), nepoužit {zf} (zdrojová)'
|
||||||
|
else:
|
||||||
|
raise ValueError('Špatná odpověď, řešitel pravděpodobně neuložen')
|
||||||
|
# poznámku chceme nezahodit…
|
||||||
|
cilova.poznamka += f'\nDEBUG: Merge: Původní poznámka: {zdrojova.poznamka}'
|
||||||
|
print(f' Výsledná osoba: {cilova.__dict__}, ukládám')
|
||||||
|
cilova.save()
|
||||||
|
|
||||||
|
# Vazby: Řešitel, User, Příjemce, Organizátor, Škola.kontaktní_osoba
|
||||||
|
print('Přepojuji vazby')
|
||||||
|
ct = m.Skola.objects.filter(kontaktni_osoba=zdrojova).update(kontaktni_osoba=cilova)
|
||||||
|
print(f' Přepojeno {ct} kontaktních osob')
|
||||||
|
# Ostatní vazby vyřeší OneToOneFieldy, ale někdy nemusí existovat…
|
||||||
|
ct = m.Resitel.objects.filter(osoba=zdrojova).update(osoba=cilova)
|
||||||
|
print(f' Přepojeno {ct} řešitelů')
|
||||||
|
ct = m.Prijemce.objects.filter(osoba=zdrojova).update(osoba=cilova)
|
||||||
|
print(f' Přepojeno {ct} příjemců')
|
||||||
|
ct = m.Organizator.objects.filter(osoba=zdrojova).update(osoba=cilova)
|
||||||
|
print(f' Přepojeno {ct} organizátorů')
|
||||||
|
# Uživatelé vedou opačným směrem, radši chceme zkontrolovat, že jsou různí ručně:
|
||||||
|
if zdrojova.user != cilova.user:
|
||||||
|
# Jeden z nich může být nenastavený…
|
||||||
|
if zdrojova.user is None:
|
||||||
|
print('Uživatel je již v cílové osobě')
|
||||||
|
elif cilova.user is None:
|
||||||
|
print('Používám uživatele zdrojové osoby')
|
||||||
|
cilova.user = zdrojova.user
|
||||||
|
# Teď nemůžeme uložit, protože kolize uživatelů. Ukládat cílovou budeme až po smazání zdrojové.
|
||||||
|
else:
|
||||||
|
raise ValueError('Osoby mají obě uživatele, radši padám')
|
||||||
|
|
||||||
|
# Uložení a mazání
|
||||||
|
print(f'Mažu zdrojovou osobu {zdrojova.__dict__}')
|
||||||
|
zdrojova.delete()
|
||||||
|
print(f'Ukládám cílovou osobu {cilova.__dict__}')
|
||||||
|
cilova.save()
|
||||||
|
|
||||||
|
input("Potvrdit transakci osob (^C pro zrušení) ")
|
||||||
|
|
|
||||||
|
|
@ -396,3 +396,21 @@ def dataResiteluCsvResponse(queryset, columns=None, with_header=True):
|
||||||
writer.writerows(queryset_list)
|
writer.writerows(queryset_list)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class _OrganizatoriView(generic.ListView):
|
||||||
|
model = m.Organizator
|
||||||
|
template_name = 'personalni/organizatori.html'
|
||||||
|
|
||||||
|
|
||||||
|
class AktivniOrganizatoriView(_OrganizatoriView):
|
||||||
|
queryset = m.Organizator.objects.aktivni()
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(AktivniOrganizatoriView, self).get_context_data(**kwargs)
|
||||||
|
context['aktivni'] = True
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class NeaktivniOrganizatoriStariView(generic.ListView):
|
||||||
|
queryset = m.Organizator.objects.neaktivni()
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují:
|
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují.
|
||||||
|
|
||||||
- ``prednasky/`` :func:`~prednasky.views.newPrednaska`
|
|
||||||
- ``prednasky/hotovo`` :func:`~prednasky.views.Prednaska_hotovo`
|
|
||||||
- ``prednasky/metaseznam_prednasek`` (metaseznam-list) :class:`~prednasky.views.MetaSeznamListView`
|
|
||||||
- ``prednasky/seznam_prednasek/<int:seznam>/export`` (seznam-export) :func:`~prednasky.views.SeznamExportView`
|
|
||||||
- ``prednasky/seznam_prednasek/<int:seznam>/`` (seznam-list) :class:`~prednasky.views.SeznamListView`
|
|
||||||
"""
|
"""
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from personalni.utils import org_required, resitel_or_org_required
|
from personalni.utils import org_required, resitel_or_org_required
|
||||||
|
|
|
||||||
100
seminar/urls.py
100
seminar/urls.py
|
|
@ -1,110 +1,21 @@
|
||||||
"""
|
"""
|
||||||
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují:
|
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují.
|
||||||
|
|
||||||
- Organizátoři
|
|
||||||
- ``o-nas/organizatori/`` (organizatori) :class:`~seminar.views.views_all.CojemamOrganizatoriView`
|
|
||||||
- ``o-nas/organizatori/organizovali/`` (stari_organizatori) :class:`~seminar.views.views_all.CojemamOrganizatoriStariView`
|
|
||||||
- Archiv
|
|
||||||
- ``archiv/rocniky/`` (seminar_archiv_rocniky) :class:`~seminar.views.views_all.ArchivView`
|
|
||||||
- ``archiv/temata/`` (seminar_archiv_temata) :class:`~seminar.views.views_all.ArchivTemataView`
|
|
||||||
- ``rocnik/<int:rocnik>/`` (seminar_rocnik) :class:`~seminar.views.views_all.RocnikView`
|
|
||||||
- ``cislo/<int:rocnik>.<str:cislo>/`` (seminar_cislo) :class:`~seminar.views.views_all.CisloView`
|
|
||||||
- ``problem/<int:pk>/`` (seminar_problem) :func:`~seminar.views.views_all.problemView`
|
|
||||||
- Zadání
|
|
||||||
- ``aktualni/zadani/`` (seminar_aktualni_zadani) :func:`~seminar.views.views_all.AktualniZadaniView`
|
|
||||||
- ``aktualni/vysledkova-listina/`` (seminar_aktualni_vysledky) :func:`~seminar.views.views_all.ZadaniAktualniVysledkovkaView`
|
|
||||||
- ``aktualni/rocnik/`` (seminar_aktualni_rocnik) :class:`~seminar.views.views_all.AktualniRocnikRedirectView`
|
|
||||||
- Články
|
|
||||||
- ``archiv/clanky/`` (clanky_resitel) :class:`~seminar.views.views_all.ClankyResitelView`
|
|
||||||
- Orgovské stránky
|
|
||||||
- ``rocnik/<int:rocnik>/vysledkovka.tex`` (seminar_rocnik_vysledkovka) :class:`~seminar.views.views_all.RocnikVysledkovkaView`
|
|
||||||
- ``rocnik/<int:rocnik>/resitele.csv`` (seminar_rocnik_resitele_csv) :func:`~seminar.views.views_all.resiteleRocnikuCsvExportView`
|
|
||||||
- ``cislo/<int:rocnik>.<str:cislo>/vysledkovka.tex`` (seminar_cislo_vysledkovka) :class:`~seminar.views.views_all.CisloVysledkovkaView`
|
|
||||||
- ``cislo/<int:rocnik>.<str:cislo>/obalky.pdf`` (seminar_cislo_obalky) :func:`~seminar.views.views_all.cisloObalkyView`
|
|
||||||
- ``cislo/<int:rocnik>.<str:cislo>/tituly.tex`` (seminar_cislo_titul) :func:`~seminar.views.views_all.TitulyView`
|
|
||||||
- ``stav`` (stav_databaze) :func:`~seminar.views.views_all.StavDatabazeView`
|
|
||||||
- ``cislo/<int:trocnik>.<str:tcislo>/odmeny/<int:frocnik>.<str:fcislo>/`` (seminar_archiv_odmeny) :class:`~seminar.views.views_all.OdmenyView`
|
|
||||||
- Další
|
|
||||||
- `` `` (titulni_strana) :class:`~seminar.views.views_all.TitulniStranaView`
|
|
||||||
- ``jak-resit/`` (jak_resit) :class:`~seminar.views.views_all.JakResitView`
|
|
||||||
- ``stare-novinky/`` (stare_novinky) :class:`~seminar.views.views_all.StareNovinkyView`
|
|
||||||
"""
|
"""
|
||||||
from django.urls import path, include, re_path
|
from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
from personalni.views import AktivniOrganizatoriView, NeaktivniOrganizatoriStariView
|
||||||
from personalni.utils import org_required
|
from personalni.utils import org_required
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# path('aktualni/temata/', views.TemataRozcestnikView),
|
|
||||||
# path('<int:rocnik>/t<int:tematko>/', views.TematkoView),
|
|
||||||
|
|
||||||
# Organizatori
|
# Organizatori
|
||||||
path('o-nas/organizatori/', views.CojemamOrganizatoriView.as_view(), name='organizatori'),
|
path('o-nas/organizatori/', AktivniOrganizatoriView.as_view(), name='organizatori'),
|
||||||
path('o-nas/organizatori/organizovali/', views.CojemamOrganizatoriStariView.as_view(), name='stari_organizatori'),
|
path('o-nas/organizatori/organizovali/', NeaktivniOrganizatoriStariView.as_view(), name='stari_organizatori'),
|
||||||
|
|
||||||
# Archiv
|
|
||||||
path('archiv/rocniky/', views.ArchivView.as_view(), name="seminar_archiv_rocniky"),
|
|
||||||
path('archiv/temata/', views.ArchivTemataView.as_view(), name="seminar_archiv_temata"),
|
|
||||||
|
|
||||||
path('rocnik/<int:rocnik>/', views.RocnikView.as_view(), name='seminar_rocnik'),
|
|
||||||
path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'),
|
|
||||||
path('problem/<int:pk>/', views.problemView, name='seminar_problem'),
|
|
||||||
|
|
||||||
# Zadani
|
|
||||||
# path('aktualni/zadani/', views.AktualniZadaniView.as_view(), name='seminar_aktualni_zadani'), # Dočasně ad-hoc jednoduchá věc.
|
|
||||||
path('aktualni/zadani/', views.AktualniZadaniView, name='seminar_aktualni_zadani'),
|
|
||||||
#path('aktualni/temata/', views.ZadaniTemataView, name='seminar_temata'),
|
|
||||||
path('aktualni/vysledkova-listina/', views.ZadaniAktualniVysledkovkaView, name='seminar_aktualni_vysledky'),
|
|
||||||
path('aktualni/rocnik/', views.AktualniRocnikRedirectView.as_view(), name='seminar_aktualni_rocnik'),
|
|
||||||
|
|
||||||
# Clanky
|
|
||||||
path('archiv/clanky/', views.ClankyResitelView.as_view(), name='clanky_resitel'),
|
|
||||||
#path('clanky/org/', views.ClankyOrganizatorView.as_view(), name='clanky_organizator'),
|
|
||||||
|
|
||||||
# Stranky viditelne pouze pro orgy:
|
|
||||||
path(
|
|
||||||
'rocnik/<int:rocnik>/vysledkovka.tex',
|
|
||||||
org_required(views.RocnikVysledkovkaView.as_view()),
|
|
||||||
name='seminar_rocnik_vysledkovka'
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
'rocnik/<int:rocnik>/resitele.csv',
|
|
||||||
org_required(views.resiteleRocnikuCsvExportView),
|
|
||||||
name='seminar_rocnik_resitele_csv'
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
'rocnik/<int:rocnik>/tituly.tex',
|
|
||||||
org_required(views.TitulyViewRocnik),
|
|
||||||
name='seminar_rocnik_titul'
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
'rocnik/<int:rocnik>/posledni_vysledkovka.tex',
|
|
||||||
org_required(views.PosledniCisloVysledkovkaView.as_view()),
|
|
||||||
name='seminar_rocnik_posledni_vysledkovka'
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
'cislo/<int:rocnik>.<str:cislo>/vysledkovka.tex',
|
|
||||||
org_required(views.CisloVysledkovkaView.as_view()),
|
|
||||||
name='seminar_cislo_vysledkovka'
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
'cislo/<int:rocnik>.<str:cislo>/obalky.pdf',
|
|
||||||
org_required(views.cisloObalkyView),
|
|
||||||
name='seminar_cislo_obalky'
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
'cislo/<int:rocnik>.<str:cislo>/tituly.tex',
|
|
||||||
org_required(views.TitulyView),
|
|
||||||
name='seminar_cislo_titul'
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
'stav',
|
'stav',
|
||||||
org_required(views.StavDatabazeView),
|
org_required(views.StavDatabazeView),
|
||||||
name='stav_databaze'
|
name='stav_databaze'
|
||||||
),
|
),
|
||||||
path(
|
|
||||||
'cislo/<int:trocnik>.<str:tcislo>/odmeny/<int:frocnik>.<str:fcislo>/',
|
|
||||||
org_required(views.OdmenyView.as_view()),
|
|
||||||
name="seminar_archiv_odmeny"),
|
|
||||||
|
|
||||||
path('', views.TitulniStranaView.as_view(), name='titulni_strana'),
|
path('', views.TitulniStranaView.as_view(), name='titulni_strana'),
|
||||||
path('jak-resit/', views.JakResitView.as_view(), name='jak_resit'),
|
path('jak-resit/', views.JakResitView.as_view(), name='jak_resit'),
|
||||||
|
|
@ -116,4 +27,5 @@ urlpatterns = [
|
||||||
org_required(views.HromadnePridaniView.as_view()),
|
org_required(views.HromadnePridaniView.as_view()),
|
||||||
name="hromadne_pridani"
|
name="hromadne_pridani"
|
||||||
),
|
),
|
||||||
|
path('problem/<int:pk>/', views.problemView, name='seminar_problem'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
234
seminar/utils.py
234
seminar/utils.py
|
|
@ -1,100 +1,6 @@
|
||||||
import datetime
|
import datetime
|
||||||
from django import views as DjangoViews
|
from django import views as DjangoViews
|
||||||
|
|
||||||
from django.db import transaction
|
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
|
|
||||||
import seminar.models as m
|
|
||||||
import treenode.treelib as t
|
|
||||||
|
|
||||||
|
|
||||||
def histogram(seznam):
|
|
||||||
d = {}
|
|
||||||
for i in seznam:
|
|
||||||
if i not in d:
|
|
||||||
d[i] = 0
|
|
||||||
d[i] += 1
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
|
||||||
def seznam_problemu():
|
|
||||||
"""Funkce pro hledání nekonzistencí v databázi a dalších nežádoucích stavů webu/databáze.
|
|
||||||
|
|
||||||
Nijak nesouvisí s Problémy zadanými řešitelům."""
|
|
||||||
# FIXME: přejmenovat funkci?
|
|
||||||
# FIXME: Tak, jak je napsaná, asi spíš patří někam k views a ne do utils (?)
|
|
||||||
problemy = []
|
|
||||||
|
|
||||||
# Pomocna fce k formatovani problemovych hlasek
|
|
||||||
def prb(cls, msg, objs=None):
|
|
||||||
s = '<b>%s:</b> %s' % (cls.__name__, msg)
|
|
||||||
if objs:
|
|
||||||
s += ' ['
|
|
||||||
for o in objs:
|
|
||||||
try:
|
|
||||||
url = o.admin_url()
|
|
||||||
except:
|
|
||||||
url = None
|
|
||||||
if url:
|
|
||||||
s += '<a href="%s">%s</a>, ' % (url, o.pk,)
|
|
||||||
else:
|
|
||||||
s += '%s, ' % (o.pk,)
|
|
||||||
s = s[:-2] + ']'
|
|
||||||
problemy.append(s)
|
|
||||||
|
|
||||||
# Duplicita jmen
|
|
||||||
jmena = {}
|
|
||||||
for r in m.Resitel.objects.all():
|
|
||||||
j = r.osoba.plne_jmeno()
|
|
||||||
if j not in jmena:
|
|
||||||
jmena[j] = []
|
|
||||||
jmena[j].append(r)
|
|
||||||
for j in jmena:
|
|
||||||
if len(jmena[j]) > 1:
|
|
||||||
prb(m.Resitel, 'Duplicitní jméno "%s"' % (j,), jmena[j])
|
|
||||||
|
|
||||||
# Data maturity a narození
|
|
||||||
for r in m.Resitel.objects.all():
|
|
||||||
if not r.rok_maturity:
|
|
||||||
prb(m.Resitel, 'Neznámý rok maturity', [r])
|
|
||||||
if r.rok_maturity and (r.rok_maturity < 1990 or r.rok_maturity > datetime.date.today().year + 10):
|
|
||||||
prb(m.Resitel, 'Podezřelé datum maturity', [r])
|
|
||||||
if r.osoba.datum_narozeni and (
|
|
||||||
r.osoba.datum_narozeni.year < 1970 or r.osoba.datum_narozeni.year > datetime.date.today().year - 12):
|
|
||||||
prb(m.Resitel, 'Podezřelé datum narození', [r])
|
|
||||||
# if not r.email:
|
|
||||||
# prb(Resitel, u'Neznámý email', [r])
|
|
||||||
|
|
||||||
## Kontroly konzistence databáze a TreeNodů
|
|
||||||
|
|
||||||
# Články
|
|
||||||
for clanek in m.Clanek.objects.all():
|
|
||||||
# získáme řešení svázané se článkem a z něj node ve stromě
|
|
||||||
reseni = clanek.reseni_set
|
|
||||||
if (reseni.count() != 1):
|
|
||||||
raise ValueError("Článek k sobě má nejedno řešení!")
|
|
||||||
r = reseni.first()
|
|
||||||
clanek_node = r.text_cely # vazba na ReseniNode z Reseni
|
|
||||||
# content type je věc pomáhající rozeznávat různé typy objektů v django-polymorphic
|
|
||||||
# protože isinstance vrátí vždy jen TreeNode
|
|
||||||
# https://django-polymorphic.readthedocs.io/en/stable/migrating.html
|
|
||||||
cislonode_ct = ContentType.objects.get_for_model(m.CisloNode)
|
|
||||||
node = clanek_node
|
|
||||||
while node is not None:
|
|
||||||
node_ct = node.polymorphic_ctype
|
|
||||||
if node_ct == cislonode_ct: # dostali jsme se k CisloNode
|
|
||||||
# zkontrolujeme, že stromové číslo odpovídá atributu
|
|
||||||
# .cislonode je opačná vazba k treenode_ptr, abychom z TreeNode dostali
|
|
||||||
# CisloNode
|
|
||||||
if clanek.cislo != node.cislonode.cislo:
|
|
||||||
prb(m.Clanek, "Číslo otištění uložené u článku nesedí s "
|
|
||||||
"číslem otištění podle struktury treenodů.", [clanek])
|
|
||||||
break
|
|
||||||
node = t.get_parent(node)
|
|
||||||
|
|
||||||
return problemy
|
|
||||||
|
|
||||||
|
|
||||||
def viewMethodSwitch(get, post):
|
def viewMethodSwitch(get, post):
|
||||||
"""
|
"""
|
||||||
|
|
@ -123,144 +29,4 @@ def viewMethodSwitch(get, post):
|
||||||
return NewView.as_view()
|
return NewView.as_view()
|
||||||
|
|
||||||
|
|
||||||
def sync_skoly(base_url):
|
|
||||||
"""Stáhne všechny školy z mamwebu na adrese <base_url> a uloží je do databáze"""
|
|
||||||
from django.urls import reverse
|
|
||||||
full_url = base_url.rstrip('/') + reverse('export_skoly')
|
|
||||||
import requests
|
|
||||||
from django.core import serializers
|
|
||||||
json = requests.get(full_url, stream=True).content
|
|
||||||
for skola in serializers.deserialize('json', json):
|
|
||||||
skola.save()
|
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def merge_resitele(cilovy, zdrojovy):
|
|
||||||
"""Spojí dva řešitelské objekty do cílového.
|
|
||||||
|
|
||||||
Pojmenování "zdrojový" je silně nepřiléhající, ale co už…"""
|
|
||||||
|
|
||||||
# Postup:
|
|
||||||
# Sjednotit / upravit informace cílového řešitele
|
|
||||||
print('Upravuji data modelu')
|
|
||||||
fieldy_shoda = ['skola', 'rok_maturity', 'zasilat', 'zasilat_cislo_emailem', 'zasilat_cislo_papirove']
|
|
||||||
|
|
||||||
for f in fieldy_shoda:
|
|
||||||
zf = getattr(zdrojovy, f)
|
|
||||||
cf = getattr(cilovy, f)
|
|
||||||
if cf == zf:
|
|
||||||
print(f' Údaj {f} je shodný ({zf})')
|
|
||||||
else:
|
|
||||||
if zf is None:
|
|
||||||
print(f' Údaj {f} je pouze v cílovém, používám')
|
|
||||||
continue
|
|
||||||
if cf is None:
|
|
||||||
setattr(cilovy, f, zf)
|
|
||||||
cilovy.poznamka += f'\nDEBUG: Merge: doplnéný údaj {f} ze zdrojového: {zf}'
|
|
||||||
print(f" Přiřazuji {f} ze zdrojového: {zf}")
|
|
||||||
continue
|
|
||||||
# Jsou fakt různé…
|
|
||||||
# FIXME: chybí možnost na vlastní úpravu…
|
|
||||||
verdikt = input(f"\n\n Údaj {f} se u řešitele {cilovy} ({cilovy.id}) liší:\n Zdrojový: {zf}\n Cílový: {cf}\n Který použít, [z]drojový, [c]ílový? ")
|
|
||||||
verdikt = verdikt[0].casefold()
|
|
||||||
if verdikt == 'z':
|
|
||||||
setattr(cilovy, f, zf)
|
|
||||||
cilovy.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {zf} (zdrojový), nepoužit {cf} (cílový)'
|
|
||||||
elif verdikt == 'c':
|
|
||||||
cilovy.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {cf} (cílový), nepoužit {zf} (zdrojový)'
|
|
||||||
else: raise ValueError('Špatná odpověď, řešitel pravděpodobně neuložen')
|
|
||||||
# poznámku chceme nezahodit…
|
|
||||||
cilovy.poznamka += f'\nDEBUG: Merge: Původní poznámka: {zdrojovy.poznamka}'
|
|
||||||
print(f' Výsledný řešitel: {cilovy.__dict__}, ukládám')
|
|
||||||
cilovy.save()
|
|
||||||
|
|
||||||
|
|
||||||
# Přepojit všechny vazby ze zdrojového na cílového
|
|
||||||
print('Přepojuji vazby')
|
|
||||||
# Vazby: Škola (hotovo), Řešení_Řešitelé, Konfery_Účastníci, Soustředění_Účastníci, Osoba (vyřeší se později, nejde přepojit)
|
|
||||||
ct = m.Reseni_Resitele.objects.filter(resitele=zdrojovy).update(resitele=cilovy)
|
|
||||||
print(f' Přepojeno {ct} řešení')
|
|
||||||
ct = m.Konfery_Ucastnici.objects.filter(resitel=zdrojovy).update(resitel=cilovy)
|
|
||||||
print(f' Přepojeno {ct} konfer')
|
|
||||||
ct = m.Soustredeni_Ucastnici.objects.filter(resitel=zdrojovy).update(resitel=cilovy)
|
|
||||||
print(f' Přepojeno {ct} sousů')
|
|
||||||
|
|
||||||
# Teď by na zdrojovém řešiteli nemělo nic viset, smazat ho, pamatujíce si jeho Osobu
|
|
||||||
zdrosoba = zdrojovy.osoba
|
|
||||||
print(f'Mažu zdrojového řešitele {zdrojovy.__dict__}')
|
|
||||||
zdrojovy.delete()
|
|
||||||
# Spojit osoby (separátní funkce).
|
|
||||||
merge_osoby(cilovy.osoba, zdrosoba)
|
|
||||||
|
|
||||||
input("Potvrdit transakci řešitelů (^C pro zrušení) ")
|
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def merge_osoby(cilova, zdrojova):
|
|
||||||
""" Spojí dvě osoby do cílové
|
|
||||||
|
|
||||||
Nehlídá omezení typu "max 1 řešitel na osobu", to by měla hlídat databáze (OneToOneField)."""
|
|
||||||
# Sjednocení dat
|
|
||||||
print('Sjednocuji data osob')
|
|
||||||
# ID, User neřešíme, poznámku vyřešíme separátně.
|
|
||||||
fieldy = ['datum_narozeni', 'datum_registrace', 'datum_souhlasu_udaje',
|
|
||||||
'datum_souhlasu_zasilani', 'email', 'foto', 'jmeno', 'mesto',
|
|
||||||
'pohlavi_muz', 'prezdivka', 'prijmeni', 'psc', 'stat', 'telefon', 'ulice']
|
|
||||||
for f in fieldy:
|
|
||||||
zf = getattr(zdrojova, f)
|
|
||||||
cf = getattr(cilova, f)
|
|
||||||
if cf == zf:
|
|
||||||
print(f' Údaj {f} je shodný ({zf})')
|
|
||||||
else:
|
|
||||||
if zf is None:
|
|
||||||
print(f' Údaj {f} je pouze v cílové, používám')
|
|
||||||
continue
|
|
||||||
if cf is None:
|
|
||||||
setattr(cilova, f, zf)
|
|
||||||
cilova.poznamka += f'\nDEBUG: Merge: doplnéný údaj {f} ze zdrojové: {zf}'
|
|
||||||
print(f" Přiřazuji {f} ze zdrojové: {zf}")
|
|
||||||
continue
|
|
||||||
# Jsou fakt různé…
|
|
||||||
# FIXME: chybí možnost na vlastní úpravu…
|
|
||||||
verdikt = input(f"\n\n Údaj {f} se u osoby {cilova} ({cilova.id}) liší:\n Zdrojový: {zf}\n Cílový: {cf}\n Který použít, [z]drojový, [c]ílový? ")
|
|
||||||
verdikt = verdikt[0].casefold()
|
|
||||||
if verdikt == 'z':
|
|
||||||
setattr(cilova, f, zf)
|
|
||||||
cilova.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {zf} (zdrojová), nepoužit {cf} (cílová)'
|
|
||||||
elif verdikt == 'c':
|
|
||||||
cilova.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {cf} (cílová), nepoužit {zf} (zdrojová)'
|
|
||||||
else: raise ValueError('Špatná odpověď, řešitel pravděpodobně neuložen')
|
|
||||||
# poznámku chceme nezahodit…
|
|
||||||
cilova.poznamka += f'\nDEBUG: Merge: Původní poznámka: {zdrojova.poznamka}'
|
|
||||||
print(f' Výsledná osoba: {cilova.__dict__}, ukládám')
|
|
||||||
cilova.save()
|
|
||||||
|
|
||||||
# Vazby: Řešitel, User, Příjemce, Organizátor, Škola.kontaktní_osoba
|
|
||||||
print('Přepojuji vazby')
|
|
||||||
ct = m.Skola.objects.filter(kontaktni_osoba=zdrojova).update(kontaktni_osoba=cilova)
|
|
||||||
print(f' Přepojeno {ct} kontaktních osob')
|
|
||||||
# Ostatní vazby vyřeší OneToOneFieldy, ale někdy nemusí existovat…
|
|
||||||
ct = m.Resitel.objects.filter(osoba=zdrojova).update(osoba=cilova)
|
|
||||||
print(f' Přepojeno {ct} řešitelů')
|
|
||||||
ct = m.Prijemce.objects.filter(osoba=zdrojova).update(osoba=cilova)
|
|
||||||
print(f' Přepojeno {ct} příjemců')
|
|
||||||
ct = m.Organizator.objects.filter(osoba=zdrojova).update(osoba=cilova)
|
|
||||||
print(f' Přepojeno {ct} organizátorů')
|
|
||||||
# Uživatelé vedou opačným směrem, radši chceme zkontrolovat, že jsou různí ručně:
|
|
||||||
if zdrojova.user != cilova.user:
|
|
||||||
# Jeden z nich může být nenastavený…
|
|
||||||
if zdrojova.user is None:
|
|
||||||
print('Uživatel je již v cílové osobě')
|
|
||||||
elif cilova.user is None:
|
|
||||||
print('Používám uživatele zdrojové osoby')
|
|
||||||
cilova.user = zdrojova.user
|
|
||||||
# Teď nemůžeme uložit, protože kolize uživatelů. Ukládat cílovou budeme až po smazání zdrojové.
|
|
||||||
else: raise ValueError('Osoby mají obě uživatele, radši padám')
|
|
||||||
|
|
||||||
# Uložení a mazání
|
|
||||||
print(f'Mažu zdrojovou osobu {zdrojova.__dict__}')
|
|
||||||
zdrojova.delete()
|
|
||||||
print(f'Ukládám cílovou osobu {cilova.__dict__}')
|
|
||||||
cilova.save()
|
|
||||||
|
|
||||||
input("Potvrdit transakci osob (^C pro zrušení) ")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,222 +1,27 @@
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from django.http import Http404
|
|
||||||
from django.db.models import Q, Sum, Count
|
|
||||||
from django.views.generic.base import RedirectView
|
|
||||||
from django.core.exceptions import PermissionDenied
|
|
||||||
|
|
||||||
from tvorba.models.problem import Problem
|
from treenode.treelib import get_parent
|
||||||
|
from treenode.models import CisloNode
|
||||||
from tvorba.models.tema import Tema
|
from tvorba.models.tema import Tema
|
||||||
from tvorba.models.clanek import Clanek
|
from tvorba.models.clanek import Clanek
|
||||||
from tvorba.models.nastaveni import Nastaveni
|
from tvorba.models.nastaveni import Nastaveni
|
||||||
from tvorba.models.rocnik import Rocnik
|
|
||||||
from tvorba.models.cislo import Cislo
|
|
||||||
from tvorba.models.deadline import Deadline
|
from tvorba.models.deadline import Deadline
|
||||||
from personalni.models.organizator import Organizator
|
|
||||||
from personalni.models.resitel import Resitel
|
from personalni.models.resitel import Resitel
|
||||||
from seminar.models.novinky import Novinky
|
from seminar.models.novinky import Novinky
|
||||||
|
|
||||||
#from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva
|
#from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva
|
||||||
from seminar import utils
|
|
||||||
from treenode import treelib
|
|
||||||
import treenode.templatetags as tnltt
|
|
||||||
import treenode.serializers as vr
|
|
||||||
from vysledkovky.utils import body_resitelu, VysledkovkaCisla, \
|
|
||||||
VysledkovkaRocniku, VysledkovkaDoTeXu
|
|
||||||
|
|
||||||
from datetime import date, datetime
|
from datetime import date
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from itertools import groupby
|
|
||||||
from collections import OrderedDict
|
|
||||||
import tempfile
|
|
||||||
import subprocess
|
|
||||||
import shutil
|
|
||||||
import os
|
|
||||||
import os.path as op
|
|
||||||
from django.conf import settings
|
|
||||||
import unicodedata
|
|
||||||
import time
|
|
||||||
|
|
||||||
# ze starého modelu
|
|
||||||
#def verejna_temata(rocnik):
|
|
||||||
# """
|
|
||||||
# Vrací queryset zveřejněných témat v daném ročníku.
|
|
||||||
# """
|
|
||||||
# return Problem.objects.filter(typ=Problem.TYP_TEMA, cislo_zadani__rocnik=rocnik, cislo_zadani__verejne_db=True).order_by('kod')
|
|
||||||
#
|
|
||||||
#def temata_v_rocniku(rocnik):
|
|
||||||
# return Problem.objects.filter(typ=Problem.TYP_TEMA, rocnik=rocnik)
|
|
||||||
|
|
||||||
def get_problemy_k_tematu(tema):
|
|
||||||
return Problem.objects.filter(nadproblem = tema)
|
|
||||||
|
|
||||||
|
|
||||||
# FIXME: Pozor, níž je ještě jeden ProblemView!
|
|
||||||
#class ProblemView(generic.DetailView):
|
|
||||||
# model = s.Problem
|
|
||||||
# # Zkopírujeme template_name od TreeNodeView, protože jsme prakticky jen trošku upravený TreeNodeView
|
|
||||||
# template_name = TreeNodeView.template_name
|
|
||||||
#
|
|
||||||
# def get_context_data(self, **kwargs):
|
|
||||||
# context = super().get_context_data(**kwargs)
|
|
||||||
# user = self.request.user
|
|
||||||
# # Teď potřebujeme doplnit tnldata do kontextu.
|
|
||||||
# # Ošklivý type switch, hezčí by bylo udělat to polymorfni. FIXME.
|
|
||||||
# if False:
|
|
||||||
# # Hezčí formátování zbytku :-P
|
|
||||||
# pass
|
|
||||||
# elif isinstance(self.object, s.Clanek) or isinstance(self.object, s.Konfera):
|
|
||||||
# # Tyhle Problémy mají ŘešeníNode
|
|
||||||
# context['tnldata'] = TNLData.from_treenode(self.object.reseninode,user)
|
|
||||||
# elif isinstance(self.object, s.Uloha):
|
|
||||||
# # FIXME: Teď vždycky zobrazujeme i vzorák! Možná by bylo hezčí/lepší mít to stejně jako pro Téma: procházet jen dosažitelné z Ročníku / čísla / whatever
|
|
||||||
# tnl_zadani = TNLData.from_treenode(self.object.ulohazadaninode,user)
|
|
||||||
# tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode,user)
|
|
||||||
# context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak])
|
|
||||||
# elif isinstance(self.object, s.Tema):
|
|
||||||
# rocniknode = self.object.rocnik.rocniknode
|
|
||||||
# context['tnldata'] = TNLData.filter_treenode(rocniknode, lambda x: isinstance(x, s.TemaVCisleNode))
|
|
||||||
# else:
|
|
||||||
# raise ValueError("Obecný problém nejde zobrazit.")
|
|
||||||
# return context
|
|
||||||
|
|
||||||
|
|
||||||
#class AktualniZadaniView(generic.TemplateView):
|
|
||||||
# template_name = 'seminar/treenode.html'
|
|
||||||
|
|
||||||
# TODO Co chceme vlastně zobrazovat na této stránce? Zatím je zde aktuální číslo, ale může tu být cokoli jiného...
|
|
||||||
#class AktualniZadaniView(TreeNodeView):
|
|
||||||
# def get_object(self):
|
|
||||||
# nastaveni = get_object_or_404(Nastaveni)
|
|
||||||
# return nastaveni.aktualni_cislo.cislonode
|
|
||||||
#
|
|
||||||
# def get_context_data(self,**kwargs):
|
|
||||||
# nastaveni = get_object_or_404(Nastaveni)
|
|
||||||
# context = super().get_context_data(**kwargs)
|
|
||||||
# verejne = nastaveni.aktualni_cislo.verejne()
|
|
||||||
# context['verejne'] = verejne
|
|
||||||
# return context
|
|
||||||
|
|
||||||
def AktualniZadaniView(request):
|
|
||||||
nastaveni = get_object_or_404(Nastaveni)
|
|
||||||
verejne = nastaveni.aktualni_cislo.verejne()
|
|
||||||
return render(request, 'seminar/zadani/AktualniZadani.html',
|
|
||||||
{'nastaveni': nastaveni,
|
|
||||||
'verejne': verejne,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
def ZadaniTemataView(request):
|
|
||||||
nastaveni = get_object_or_404(Nastaveni)
|
|
||||||
verejne = nastaveni.aktualni_cislo.verejne()
|
|
||||||
akt_rocnik = nastaveni.aktualni_cislo.rocnik
|
|
||||||
temata = Tema.objects.filter(rocnik=akt_rocnik, stav='zadany')
|
|
||||||
return render(request, 'seminar/tematka/rozcestnik.html',
|
|
||||||
{
|
|
||||||
'tematka': temata,
|
|
||||||
'verejne': verejne,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# nastaveni = get_object_or_404(Nastaveni)
|
|
||||||
# temata = verejna_temata(nastaveni.aktualni_rocnik)
|
|
||||||
# for t in temata:
|
|
||||||
# if request.user.is_staff:
|
|
||||||
# t.prispevky = t.prispevek_set.filter(problem=t)
|
|
||||||
# else:
|
|
||||||
# t.prispevky = t.prispevek_set.filter(problem=t, zverejnit=True)
|
|
||||||
# return render(request, 'seminar/zadani/Temata.html',
|
|
||||||
# {
|
|
||||||
# 'temata': temata,
|
|
||||||
# }
|
|
||||||
# )
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#def TematkoView(request, rocnik, tematko):
|
|
||||||
# nastaveni = s.Nastaveni.objects.first()
|
|
||||||
# rocnik_object = s.Rocnik.objects.filter(rocnik=rocnik)
|
|
||||||
# tematko_object = s.Tema.objects.filter(rocnik=rocnik_object[0], kod=tematko)
|
|
||||||
# seznam = vytahniZLesaSeznam(tematko_object[0], nastaveni.aktualni_rocnik().rocniknode)
|
|
||||||
# for node, depth in seznam:
|
|
||||||
# if node.isinstance(node, s.KonferaNode):
|
|
||||||
# raise Exception("Not implemented yet")
|
|
||||||
# if node.isinstance(node, s.PohadkaNode): # Mohu ignorovat, má pod sebou
|
|
||||||
# pass
|
|
||||||
#
|
|
||||||
# return render(request, 'seminar/tematka/toaletak.html', {})
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#def TemataRozcestnikView(request):
|
|
||||||
# print("=============================================")
|
|
||||||
# nastaveni = s.Nastaveni.objects.first()
|
|
||||||
# tematka_objects = s.Tema.objects.filter(rocnik=nastaveni.aktualni_rocnik())
|
|
||||||
# tematka = [] #List tematka obsahuje pro kazde tematko object a list vsech TemaVCisleNodu - implementované pomocí slovníku
|
|
||||||
# for tematko_object in tematka_objects:
|
|
||||||
# print("AKTUALNI TEMATKO")
|
|
||||||
# print(tematko_object.id)
|
|
||||||
# odkazy = vytahniZLesaSeznam(tematko_object, nastaveni.aktualni_rocnik().rocniknode, pouze_zajimave = True) #Odkazy jsou tuply (node, depth) v listu
|
|
||||||
# print(odkazy)
|
|
||||||
# cisla = [] # List tuplů (nazev cisla, list odkazů)
|
|
||||||
# vcisle = []
|
|
||||||
# cislo = None
|
|
||||||
# for odkaz in odkazy:
|
|
||||||
# if odkaz[1] == 0:
|
|
||||||
# if cislo != None:
|
|
||||||
# cisla.append((cislo, vcisle))
|
|
||||||
# cislo = (odkaz[0].getOdkazStr(), odkaz[0].getOdkaz())
|
|
||||||
# vcisle = []
|
|
||||||
# else:
|
|
||||||
# print(odkaz[0].getOdkaz())
|
|
||||||
# vcisle.append((odkaz[0].getOdkazStr(), odkaz[0].getOdkaz()))
|
|
||||||
# if cislo != None:
|
|
||||||
# cisla.append((cislo, vcisle))
|
|
||||||
#
|
|
||||||
# print(cisla)
|
|
||||||
# tematka.append({
|
|
||||||
# "kod" : tematko_object.kod,
|
|
||||||
# "nazev" : tematko_object.nazev,
|
|
||||||
# "abstrakt" : tematko_object.abstrakt,
|
|
||||||
# "obrazek": tematko_object.obrazek,
|
|
||||||
# "cisla" : cisla
|
|
||||||
# })
|
|
||||||
# return render(request, 'seminar/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik})
|
|
||||||
#
|
|
||||||
|
|
||||||
def ZadaniAktualniVysledkovkaView(request):
|
|
||||||
nastaveni = get_object_or_404(Nastaveni)
|
|
||||||
# Aktualni verejna vysledkovka
|
|
||||||
rocnik = nastaveni.aktualni_rocnik
|
|
||||||
context = {'vysledkovka': VysledkovkaRocniku(rocnik, True)}
|
|
||||||
|
|
||||||
# kdyz neni verejna vysledkovka, tak zobraz starou
|
|
||||||
if len(context['vysledkovka'].cisla_rocniku) == 0:
|
|
||||||
try:
|
|
||||||
minuly_rocnik = Rocnik.objects.get(
|
|
||||||
rocnik=(rocnik.rocnik-1))
|
|
||||||
rocnik = minuly_rocnik
|
|
||||||
|
|
||||||
# Přepíšeme prázdnou výsledkovku výsledkovkou z minulého ročníku
|
|
||||||
context['vysledkovka'] = VysledkovkaRocniku(rocnik, True)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
pass
|
|
||||||
|
|
||||||
context['rocnik'] = rocnik
|
|
||||||
return render(
|
|
||||||
request,
|
|
||||||
'seminar/zadani/AktualniVysledkovka.html',
|
|
||||||
context
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
### Titulni strana
|
### Titulni strana
|
||||||
|
|
||||||
def spravne_novinky(request):
|
|
||||||
|
def _spravne_novinky(request):
|
||||||
"""
|
"""
|
||||||
Vrátí správný QuerySet novinek, tedy ten, který daný uživatel smí vidět.
|
Vrátí správný QuerySet novinek, tedy ten, který daný uživatel smí vidět.
|
||||||
Tj. Organizátorům všechny, ostatním jen veřejné
|
Tj. Organizátorům všechny, ostatním jen veřejné
|
||||||
|
|
@ -228,7 +33,8 @@ def spravne_novinky(request):
|
||||||
qs = qs.filter(zverejneno=True)
|
qs = qs.filter(zverejneno=True)
|
||||||
return qs.order_by('-datum')
|
return qs.order_by('-datum')
|
||||||
|
|
||||||
def aktualni_temata(rocnik):
|
|
||||||
|
def _aktualni_temata(rocnik):
|
||||||
"""
|
"""
|
||||||
Vrací PolymorphicQuerySet témat v daném ročníku, ke kterým se aktuálně dá něco odevzdat.
|
Vrací PolymorphicQuerySet témat v daném ročníku, ke kterým se aktuálně dá něco odevzdat.
|
||||||
"""
|
"""
|
||||||
|
|
@ -239,7 +45,7 @@ class TitulniStranaView(generic.ListView):
|
||||||
template_name= 'seminar/titulnistrana/titulnistrana.html'
|
template_name= 'seminar/titulnistrana/titulnistrana.html'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return spravne_novinky(self.request)[:3]
|
return _spravne_novinky(self.request)[:3]
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(TitulniStranaView, self).get_context_data(**kwargs)
|
context = super(TitulniStranaView, self).get_context_data(**kwargs)
|
||||||
|
|
@ -250,7 +56,7 @@ class TitulniStranaView(generic.ListView):
|
||||||
|
|
||||||
# Aktuální témata
|
# Aktuální témata
|
||||||
nazvy_a_odkazy_na_aktualni_temata = []
|
nazvy_a_odkazy_na_aktualni_temata = []
|
||||||
akt_temata = aktualni_temata(nastaveni.aktualni_rocnik)
|
akt_temata = _aktualni_temata(nastaveni.aktualni_rocnik)
|
||||||
|
|
||||||
for tema in akt_temata:
|
for tema in akt_temata:
|
||||||
# FIXME: netuším, jestli funguje tema.verejne_url(), nemáme testdata na témátka - je to asi url vzhledem k ročníku
|
# FIXME: netuším, jestli funguje tema.verejne_url(), nemáme testdata na témátka - je to asi url vzhledem k ročníku
|
||||||
|
|
@ -262,406 +68,112 @@ class TitulniStranaView(generic.ListView):
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class StareNovinkyView(generic.ListView):
|
class StareNovinkyView(generic.ListView):
|
||||||
template_name = 'seminar/stare_novinky.html'
|
template_name = 'seminar/stare_novinky.html'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return spravne_novinky(self.request)
|
return _spravne_novinky(self.request)
|
||||||
|
|
||||||
### Co je M&M
|
|
||||||
|
|
||||||
|
|
||||||
# Organizatori
|
|
||||||
def aktivniOrganizatori(datum=timezone.now()):
|
|
||||||
return Organizator.objects.exclude(
|
|
||||||
organizuje_do__isnull=False,
|
|
||||||
organizuje_do__lt=datum
|
|
||||||
).order_by('osoba__jmeno')
|
|
||||||
|
|
||||||
|
|
||||||
class CojemamOrganizatoriView(generic.ListView):
|
|
||||||
model = Organizator
|
|
||||||
template_name = 'seminar/cojemam/organizatori.html'
|
|
||||||
queryset = aktivniOrganizatori()
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(CojemamOrganizatoriView, self).get_context_data(**kwargs)
|
|
||||||
context['aktivni'] = True
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class CojemamOrganizatoriStariView(generic.ListView):
|
|
||||||
model = Organizator
|
|
||||||
template_name = 'seminar/cojemam/organizatori.html'
|
|
||||||
queryset = Organizator.objects.exclude(
|
|
||||||
id__in=aktivniOrganizatori()).order_by('-organizuje_do')
|
|
||||||
|
|
||||||
### Archiv
|
|
||||||
|
|
||||||
|
|
||||||
class ArchivView(generic.ListView):
|
|
||||||
model = Rocnik
|
|
||||||
template_name='seminar/archiv/cisla.html'
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(ArchivView, self).get_context_data(**kwargs)
|
|
||||||
|
|
||||||
cisla = Cislo.objects.filter(poradi=1)
|
|
||||||
if not self.request.user.je_org:
|
|
||||||
cisla = cisla.filter(verejne_db=True)
|
|
||||||
urls ={}
|
|
||||||
|
|
||||||
for i, c in enumerate(cisla):
|
|
||||||
# Výchozí nastavení
|
|
||||||
if c.rocnik not in urls:
|
|
||||||
urls[c.rocnik] = op.join(settings.STATIC_URL, "images", "no-picture.png")
|
|
||||||
# NOTE: tohle možná nastavuje poslední titulku
|
|
||||||
if c.titulka_nahled:
|
|
||||||
urls[c.rocnik] = c.titulka_nahled.url
|
|
||||||
|
|
||||||
context["object_list"] = urls
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RocnikView(generic.DetailView):
|
|
||||||
model = Rocnik
|
|
||||||
template_name = 'seminar/archiv/rocnik.html'
|
|
||||||
|
|
||||||
# Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik)
|
|
||||||
def get_object(self, queryset=None):
|
|
||||||
if queryset is None:
|
|
||||||
queryset = self.get_queryset()
|
|
||||||
|
|
||||||
return get_object_or_404(queryset,rocnik=self.kwargs.get('rocnik'))
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(RocnikView, self).get_context_data(**kwargs)
|
|
||||||
context["vysledkovka"] = VysledkovkaRocniku(context["rocnik"], True)
|
|
||||||
context["neprazdna_vysledkovka"] = len(context['vysledkovka'].cisla_rocniku) != 0
|
|
||||||
context["vysledkovka_neverejna"] = VysledkovkaRocniku(context["rocnik"], False)
|
|
||||||
return context
|
|
||||||
|
|
||||||
def resiteleRocnikuCsvExportView(request, rocnik):
|
|
||||||
from personalni.views import dataResiteluCsvResponse
|
|
||||||
assert request.method in ('GET', 'HEAD')
|
|
||||||
return dataResiteluCsvResponse(
|
|
||||||
Resitel.objects.resi_v_rocniku(
|
|
||||||
get_object_or_404(Rocnik, rocnik=rocnik)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# FIXME: Pozor, výš je ještě jeden ProblemView!
|
|
||||||
#class ProblemView(generic.DetailView):
|
|
||||||
# model = Problem
|
|
||||||
#
|
|
||||||
# # Používáme funkci, protože přímo template_name neumí mít v přiřazení dost logiky. Ledaže by se to udělalo polymorfně...
|
|
||||||
# def get_template_names(self, **kwargs):
|
|
||||||
# # FIXME: Switch podle typu není hezký, ale nechtělo se mi to přepisovat celé. Správně by se tohle mělo řešit polymorfismem.
|
|
||||||
# spravne_templaty = {
|
|
||||||
# s.Uloha: "uloha",
|
|
||||||
# s.Tema: "tema",
|
|
||||||
# s.Konfera: "konfera",
|
|
||||||
# s.Clanek: "clanek",
|
|
||||||
# }
|
|
||||||
# context = super().get_context_data(**kwargs)
|
|
||||||
# return ['seminar/archiv/problem_' + spravne_templaty[context['object'].__class__] + '.html']
|
|
||||||
#
|
|
||||||
# def get_context_data(self, **kwargs):
|
|
||||||
# context = super().get_context_data(**kwargs)
|
|
||||||
# # Musí se používat context['object'], protože nevíme, jestli dostaneme úložku, téma, článek, .... a tyhle věci vyrábějí různé klíče.
|
|
||||||
# if not context['object'].verejne() and not self.request.user.je_org:
|
|
||||||
# raise PermissionDenied()
|
|
||||||
# if isinstance(context['object'], Clanek):
|
|
||||||
# context['reseni'] = Reseni.objects.filter(problem=context['object']).select_related('resitel').order_by('resitel__prijmeni')
|
|
||||||
# return context
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CisloView(generic.DetailView):
|
|
||||||
# FIXME zobrazování témátek a vůbec, teď je tam jen odkaz na číslo v pdf
|
|
||||||
model = Cislo
|
|
||||||
template_name = 'seminar/archiv/cislo.html'
|
|
||||||
|
|
||||||
# Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik)
|
|
||||||
def get_object(self, queryset=None):
|
|
||||||
if queryset is None:
|
|
||||||
queryset = self.get_queryset()
|
|
||||||
rocnik_arg = self.kwargs.get('rocnik')
|
|
||||||
poradi_arg = self.kwargs.get('cislo')
|
|
||||||
queryset = queryset.filter(rocnik__rocnik=rocnik_arg, poradi=poradi_arg)
|
|
||||||
|
|
||||||
try:
|
|
||||||
obj = queryset.get()
|
|
||||||
except queryset.model.DoesNotExist:
|
|
||||||
raise Http404(_("No %(verbose_name)s found matching the query") %
|
|
||||||
{'verbose_name': queryset.model._meta.verbose_name})
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(CisloView, self).get_context_data(**kwargs)
|
|
||||||
|
|
||||||
cislo = context['cislo']
|
|
||||||
context['prevcislo'] = Cislo.objects.filter((Q(rocnik__lt=self.object.rocnik) | Q(poradi__lt=self.object.poradi))&Q(rocnik__lte=self.object.rocnik)).first()
|
|
||||||
|
|
||||||
deadliny = Deadline.objects.filter(cislo=cislo).reverse()
|
|
||||||
deadliny_s_vysledkovkami = []
|
|
||||||
|
|
||||||
nadpisy = {
|
|
||||||
Deadline.TYP_CISLA: "Výsledkovka",
|
|
||||||
Deadline.TYP_PRVNI: "Výsledkovka do prvního deadlinu",
|
|
||||||
Deadline.TYP_PRVNI_A_SOUS: "Výsledkovka do prvního deadlinu a deadlinu pro účast na soustředění",
|
|
||||||
Deadline.TYP_SOUS: "Výsledkovka do deadlinu pro účast na soustředění",
|
|
||||||
}
|
|
||||||
|
|
||||||
for deadline in deadliny:
|
|
||||||
if self.request.user.je_org | deadline.verejna_vysledkovka:
|
|
||||||
deadliny_s_vysledkovkami.append((deadline, nadpisy[deadline.typ], VysledkovkaCisla(cislo, not self.request.user.je_org, deadline)))
|
|
||||||
|
|
||||||
context['deadliny_s_vysledkovkami'] = deadliny_s_vysledkovkami
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class ArchivTemataView(generic.ListView):
|
|
||||||
model = Problem
|
|
||||||
template_name = 'seminar/archiv/temata.html'
|
|
||||||
queryset = Tema.objects.filter(stav=Problem.STAV_ZADANY).select_related('rocnik').order_by('rocnik', 'kod')
|
|
||||||
|
|
||||||
def get_context_data(self, *args, **kwargs):
|
|
||||||
ctx = super().get_context_data(*args, **kwargs)
|
|
||||||
ctx['rocniky'] = OrderedDict()
|
|
||||||
for rocnik, temata in groupby(ctx['object_list'], lambda tema: tema.rocnik):
|
|
||||||
ctx['rocniky'][rocnik] = list(temata)
|
|
||||||
return ctx
|
|
||||||
|
|
||||||
class OdmenyView(generic.TemplateView):
|
|
||||||
template_name = 'seminar/archiv/odmeny.html'
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
fromcislo = get_object_or_404(Cislo, rocnik=self.kwargs.get('frocnik'), poradi=self.kwargs.get('fcislo'))
|
|
||||||
tocislo = get_object_or_404(Cislo, rocnik=self.kwargs.get('trocnik'), poradi=self.kwargs.get('tcislo'))
|
|
||||||
resitele = Resitel.objects.aktivni_resitele(tocislo)
|
|
||||||
|
|
||||||
def get_diff(from_deadline: Deadline, to_deadline: Deadline):
|
|
||||||
frombody = body_resitelu(resitele=resitele, jen_verejne=False, do=from_deadline)
|
|
||||||
tobody = body_resitelu(resitele=resitele, jen_verejne=False, do=to_deadline)
|
|
||||||
outlist = []
|
|
||||||
for resitel in resitele:
|
|
||||||
fbody = frombody.get(resitel.id, 0)
|
|
||||||
tbody = tobody.get(resitel.id, 0)
|
|
||||||
ftitul = resitel.get_titul(fbody)
|
|
||||||
ttitul = resitel.get_titul(tbody)
|
|
||||||
if ftitul != ttitul:
|
|
||||||
outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul})
|
|
||||||
return outlist
|
|
||||||
|
|
||||||
def posledni_deadline_oprava(cislo: Cislo) -> Deadline:
|
|
||||||
posledni_deadline = cislo.posledni_deadline
|
|
||||||
if posledni_deadline is None:
|
|
||||||
return Deadline.objects.filter(Q(cislo__poradi__lt=cislo.poradi, cislo__rocnik=cislo.rocnik) | Q(cislo__rocnik__rocnik__lt=cislo.rocnik.rocnik)).order_by("deadline").last()
|
|
||||||
return posledni_deadline
|
|
||||||
|
|
||||||
context["from_cislo"] = fromcislo
|
|
||||||
context["to_cislo"] = tocislo
|
|
||||||
from_deadline = posledni_deadline_oprava(fromcislo)
|
|
||||||
to_deadline = posledni_deadline_oprava(tocislo)
|
|
||||||
context["from_deadline"] = from_deadline
|
|
||||||
context["to_deadline"] = to_deadline
|
|
||||||
context["zmeny"] = get_diff(from_deadline, to_deadline)
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Generovani vysledkovky
|
|
||||||
|
|
||||||
class CisloVysledkovkaView(CisloView):
|
|
||||||
"""View vytvořené pro stránku zobrazující výsledkovku čísla v TeXu."""
|
|
||||||
|
|
||||||
model = Cislo
|
|
||||||
template_name = 'seminar/archiv/cislo_vysledkovka.tex'
|
|
||||||
#content_type = 'application/x-tex; charset=UTF8'
|
|
||||||
#umozni rovnou stahnout TeXovsky dokument
|
|
||||||
content_type = 'text/plain; charset=UTF8'
|
|
||||||
#vypise na stranku textovy obsah vyTeXane vysledkovky k okopirovani
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(CisloVysledkovkaView, self).get_context_data()
|
|
||||||
cislo = context['cislo']
|
|
||||||
|
|
||||||
cislopred = cislo.predchozi()
|
|
||||||
if cislopred is not None:
|
|
||||||
context['vysledkovka'] = VysledkovkaDoTeXu(
|
|
||||||
cislo,
|
|
||||||
od_vyjma=cislopred.zlomovy_deadline_pro_papirove_cislo(),
|
|
||||||
do_vcetne=cislo.zlomovy_deadline_pro_papirove_cislo(),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
context['vysledkovka'] = VysledkovkaCisla(
|
|
||||||
cislo,
|
|
||||||
jen_verejne=False,
|
|
||||||
do_deadlinu=cislo.zlomovy_deadline_pro_papirove_cislo(),
|
|
||||||
)
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
# Podle předchozího
|
|
||||||
class PosledniCisloVysledkovkaView(generic.DetailView):
|
|
||||||
"""View vytvořené pro zobrazení výsledkovky posledního čísla v TeXu."""
|
|
||||||
|
|
||||||
model = Rocnik
|
|
||||||
template_name = 'seminar/archiv/cislo_vysledkovka.tex'
|
|
||||||
content_type = 'text/plain; charset=UTF8'
|
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
|
||||||
if queryset is None:
|
|
||||||
queryset = self.get_queryset()
|
|
||||||
rocnik_arg = self.kwargs.get('rocnik')
|
|
||||||
queryset = queryset.filter(rocnik=rocnik_arg)
|
|
||||||
|
|
||||||
try:
|
|
||||||
obj = queryset.get()
|
|
||||||
except queryset.model.DoesNotExist:
|
|
||||||
raise Http404(_("No %(verbose_name)s found matching the query") %
|
|
||||||
{'verbose_name': queryset.model._meta.verbose_name})
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(PosledniCisloVysledkovkaView, self).get_context_data()
|
|
||||||
rocnik = context['rocnik']
|
|
||||||
cislo = rocnik.cisla.order_by("poradi").last()
|
|
||||||
cislopred = cislo.predchozi()
|
|
||||||
context['vysledkovka'] = VysledkovkaDoTeXu(
|
|
||||||
cislo,
|
|
||||||
od_vyjma=cislopred.zlomovy_deadline_pro_papirove_cislo(),
|
|
||||||
do_vcetne=cislo.deadline_v_cisle.order_by("deadline").last(),
|
|
||||||
)
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class RocnikVysledkovkaView(RocnikView):
|
|
||||||
""" View vytvořené pro stránku zobrazující výsledkovku ročníku v TeXu."""
|
|
||||||
model = Rocnik
|
|
||||||
template_name = 'seminar/archiv/rocnik_vysledkovka.tex'
|
|
||||||
#content_type = 'application/x-tex; charset=UTF8'
|
|
||||||
#umozni rovnou stahnout TeXovsky dokument
|
|
||||||
content_type = 'text/plain; charset=UTF8'
|
|
||||||
#vypise na stranku textovy obsah vyTeXane vysledkovky k okopirovani
|
|
||||||
|
|
||||||
def cisloObalkyView(request, rocnik, cislo):
|
|
||||||
realne_cislo = get_object_or_404(Cislo, poradi=cislo, rocnik__rocnik=rocnik)
|
|
||||||
return obalkyView(request, aktivniResitele(realne_cislo))
|
|
||||||
|
|
||||||
|
|
||||||
def obalkyView(request, resitele):
|
|
||||||
tex = render(request,'seminar/archiv/obalky.tex', {'resitele': resitele}).content
|
|
||||||
|
|
||||||
tempdir = tempfile.mkdtemp()
|
|
||||||
with open(tempdir+"/obalky.tex","w") as texfile:
|
|
||||||
texfile.write(tex.decode())
|
|
||||||
shutil.copy(os.path.join(settings.STATIC_ROOT, 'seminar/lisak.pdf'), tempdir)
|
|
||||||
subprocess.call(["pdflatex","obalky.tex"], cwd = tempdir)
|
|
||||||
|
|
||||||
with open(tempdir+"/obalky.pdf","rb") as pdffile:
|
|
||||||
response = HttpResponse(pdffile.read(), content_type='application/pdf')
|
|
||||||
shutil.rmtree(tempdir)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
### Tituly
|
|
||||||
def TitulyViewRocnik(request, rocnik):
|
|
||||||
return TitulyView(request, rocnik, None)
|
|
||||||
|
|
||||||
|
|
||||||
def TitulyView(request, rocnik, cislo):
|
|
||||||
""" View pro stažení makra titulů v TeXu."""
|
|
||||||
rocnik_obj = get_object_or_404(Rocnik, rocnik = rocnik)
|
|
||||||
resitele = Resitel.objects.filter(rok_maturity__gte = rocnik_obj.prvni_rok)
|
|
||||||
|
|
||||||
asciijmena = []
|
|
||||||
jmenovci = False # detekuje, zda jsou dva řešitelé jmenovci (modulo nabodeníčka),
|
|
||||||
# pokud ano, vrátí se jako true
|
|
||||||
if cislo is not None:
|
|
||||||
cislo_obj = get_object_or_404(Cislo, rocnik=rocnik_obj, poradi=cislo)
|
|
||||||
slovnik_s_body = body_resitelu(do=cislo_obj.zlomovy_deadline_pro_papirove_cislo(), jen_verejne=False)
|
|
||||||
else:
|
|
||||||
slovnik_s_body = body_resitelu(do=Deadline.objects.filter(cislo__rocnik=rocnik_obj).last(), jen_verejne=False)
|
|
||||||
|
|
||||||
for resitel in resitele:
|
|
||||||
resitel.titul = resitel.get_titul(slovnik_s_body[resitel.id])
|
|
||||||
jmeno = resitel.osoba.jmeno+resitel.osoba.prijmeni
|
|
||||||
# převedeme jména a příjmení řešitelů do ASCII
|
|
||||||
ascii_jmeno_bytes = unicodedata.normalize('NFKD', jmeno).encode("ascii","ignore")
|
|
||||||
# vrátí se byte string, převedeme na standardní string
|
|
||||||
ascii_jmeno_divnoznaky = str(ascii_jmeno_bytes, "utf-8", "ignore").replace(" ","")
|
|
||||||
resitel.ascii = ''.join(a for a in ascii_jmeno_divnoznaky if a.isalnum())
|
|
||||||
if resitel.ascii not in asciijmena:
|
|
||||||
asciijmena.append(resitel.ascii)
|
|
||||||
else:
|
|
||||||
jmenovci = True
|
|
||||||
|
|
||||||
return render(request, 'seminar/archiv/tituly.tex',
|
|
||||||
{'resitele': resitele,'jmenovci':jmenovci},content_type="text/plain")
|
|
||||||
|
|
||||||
|
|
||||||
### Články
|
|
||||||
def group_by_rocnik(clanky):
|
|
||||||
''' Vezme zadaný seznam článků a seskupí je podle ročníku.
|
|
||||||
Vrátí seznam seznamů článků ze stejného ročníku.'''
|
|
||||||
if len(clanky) == 0:
|
|
||||||
return clanky
|
|
||||||
clanky.order_by('cislo__rocnik__rocnik')
|
|
||||||
skupiny_clanku = []
|
|
||||||
skupina = []
|
|
||||||
|
|
||||||
rocnik = clanky.first().cislo.rocnik.rocnik # první ročník
|
|
||||||
for clanek in clanky:
|
|
||||||
if clanek.cislo.rocnik.rocnik == rocnik:
|
|
||||||
skupina.append(clanek)
|
|
||||||
else:
|
|
||||||
skupiny_clanku.append(skupina)
|
|
||||||
skupina = []
|
|
||||||
skupina.append(clanek)
|
|
||||||
rocnik = clanek.cislo.rocnik.rocnik
|
|
||||||
skupiny_clanku.append(skupina)
|
|
||||||
return skupiny_clanku
|
|
||||||
|
|
||||||
|
|
||||||
# FIXME: clanky jsou vsechny, pokud budou i neresitelske, tak se take zobrazi
|
|
||||||
# FIXME: Původně tu byl kód přímo v těle třídy, což rozbíjelo migrace. Opravil jsem, ale vůbec nevím, jestli to funguje.
|
|
||||||
class ClankyResitelView(generic.ListView):
|
|
||||||
model = Problem
|
|
||||||
template_name = 'seminar/clanky/resitelske_clanky.html'
|
|
||||||
|
|
||||||
# FIXME: QuerySet není pole!
|
|
||||||
def get_queryset(self):
|
|
||||||
clanky = Clanek.objects.filter(stav=Problem.STAV_VYRESENY).select_related('cislo__rocnik').order_by('-cislo__rocnik__rocnik')
|
|
||||||
queryset = []
|
|
||||||
skupiny_clanku = group_by_rocnik(clanky)
|
|
||||||
for skupina in skupiny_clanku:
|
|
||||||
skupina.sort(key=lambda clanek: clanek.kod_v_rocniku)
|
|
||||||
for clanek in skupina:
|
|
||||||
queryset.append(clanek)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
# FIXME: pokud chceme orgoclanky, tak nejak zavest do modelu a podle toho odkomentovat a upravit
|
|
||||||
#class ClankyOrganizatorView(generic.ListView)<F12>:
|
|
||||||
# model = Problem
|
|
||||||
# template_name = 'seminar/clanky/organizatorske_clanky.html'
|
|
||||||
# queryset = Problem.objects.filter(stav=Problem.STAV_ZADANY).select_related('cislo_zadani__rocnik').order_by('-cislo_zadani__rocnik__rocnik', 'kod')
|
|
||||||
|
|
||||||
|
|
||||||
### Status
|
### Status
|
||||||
|
def _histogram(seznam):
|
||||||
|
d = {}
|
||||||
|
for i in seznam:
|
||||||
|
if i not in d:
|
||||||
|
d[i] = 0
|
||||||
|
d[i] += 1
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def _seznam_problemu():
|
||||||
|
"""Funkce pro hledání nekonzistencí v databázi a dalších nežádoucích stavů webu/databáze.
|
||||||
|
|
||||||
|
Nijak nesouvisí s Problémy zadanými řešitelům."""
|
||||||
|
# FIXME: přejmenovat funkci?
|
||||||
|
# FIXME: Tak, jak je napsaná, asi spíš patří někam k views a ne do utils (?)
|
||||||
|
problemy = []
|
||||||
|
|
||||||
|
# Pomocna fce k formatovani problemovych hlasek
|
||||||
|
def prb(cls, msg, objs=None):
|
||||||
|
s = '<b>%s:</b> %s' % (cls.__name__, msg)
|
||||||
|
if objs:
|
||||||
|
s += ' ['
|
||||||
|
for o in objs:
|
||||||
|
try:
|
||||||
|
url = o.admin_url()
|
||||||
|
except:
|
||||||
|
url = None
|
||||||
|
if url:
|
||||||
|
s += '<a href="%s">%s</a>, ' % (url, o.pk,)
|
||||||
|
else:
|
||||||
|
s += '%s, ' % (o.pk,)
|
||||||
|
s = s[:-2] + ']'
|
||||||
|
problemy.append(s)
|
||||||
|
|
||||||
|
# Duplicita jmen
|
||||||
|
jmena = {}
|
||||||
|
for r in Resitel.objects.all():
|
||||||
|
j = r.osoba.plne_jmeno()
|
||||||
|
if j not in jmena:
|
||||||
|
jmena[j] = []
|
||||||
|
jmena[j].append(r)
|
||||||
|
for j in jmena:
|
||||||
|
if len(jmena[j]) > 1:
|
||||||
|
prb(Resitel, 'Duplicitní jméno "%s"' % (j,), jmena[j])
|
||||||
|
|
||||||
|
# Data maturity a narození
|
||||||
|
for r in Resitel.objects.all():
|
||||||
|
if not r.rok_maturity:
|
||||||
|
prb(Resitel, 'Neznámý rok maturity', [r])
|
||||||
|
if r.rok_maturity and (
|
||||||
|
r.rok_maturity < 1990 or r.rok_maturity > date.today().year + 10
|
||||||
|
):
|
||||||
|
prb(Resitel, 'Podezřelé datum maturity', [r])
|
||||||
|
if r.osoba.datum_narozeni and (
|
||||||
|
r.osoba.datum_narozeni.year < 1970 or
|
||||||
|
r.osoba.datum_narozeni.year > date.today().year - 12
|
||||||
|
):
|
||||||
|
prb(Resitel, 'Podezřelé datum narození', [r])
|
||||||
|
# if not r.email:
|
||||||
|
# prb(Resitel, u'Neznámý email', [r])
|
||||||
|
|
||||||
|
## Kontroly konzistence databáze a TreeNodů
|
||||||
|
|
||||||
|
# Články
|
||||||
|
for clanek in Clanek.objects.all():
|
||||||
|
# získáme řešení svázané se článkem a z něj node ve stromě
|
||||||
|
reseni = clanek.reseni_set
|
||||||
|
if (reseni.count() != 1):
|
||||||
|
raise ValueError("Článek k sobě má nejedno řešení!")
|
||||||
|
r = reseni.first()
|
||||||
|
clanek_node = r.text_cely # vazba na ReseniNode z Reseni
|
||||||
|
# content type je věc pomáhající rozeznávat různé typy objektů v django-polymorphic
|
||||||
|
# protože isinstance vrátí vždy jen TreeNode
|
||||||
|
# https://django-polymorphic.readthedocs.io/en/stable/migrating.html
|
||||||
|
cislonode_ct = ContentType.objects.get_for_model(CisloNode)
|
||||||
|
node = clanek_node
|
||||||
|
while node is not None:
|
||||||
|
node_ct = node.polymorphic_ctype
|
||||||
|
if node_ct == cislonode_ct: # dostali jsme se k CisloNode
|
||||||
|
# zkontrolujeme, že stromové číslo odpovídá atributu
|
||||||
|
# .cislonode je opačná vazba k treenode_ptr, abychom z TreeNode dostali
|
||||||
|
# CisloNode
|
||||||
|
if clanek.cislo != node.cislonode.cislo:
|
||||||
|
prb(
|
||||||
|
Clanek,
|
||||||
|
"Číslo otištění uložené u článku nesedí s číslem otištění podle struktury treenodů.",
|
||||||
|
[clanek]
|
||||||
|
)
|
||||||
|
break
|
||||||
|
node = get_parent(node)
|
||||||
|
|
||||||
|
return problemy
|
||||||
|
|
||||||
|
|
||||||
def StavDatabazeView(request):
|
def StavDatabazeView(request):
|
||||||
# nastaveni = Nastaveni.objects.get()
|
# nastaveni = Nastaveni.objects.get()
|
||||||
problemy = utils.seznam_problemu()
|
problemy = _seznam_problemu()
|
||||||
muzi = Resitel.objects.filter(osoba__pohlavi_muz=True)
|
muzi = Resitel.objects.filter(osoba__pohlavi_muz=True)
|
||||||
zeny = Resitel.objects.filter(osoba__pohlavi_muz=False)
|
zeny = Resitel.objects.filter(osoba__pohlavi_muz=False)
|
||||||
return render(request, 'seminar/stav_databaze.html',
|
return render(request, 'seminar/stav_databaze.html',
|
||||||
|
|
@ -672,8 +184,8 @@ def StavDatabazeView(request):
|
||||||
'resitele': Resitel.objects.all(),
|
'resitele': Resitel.objects.all(),
|
||||||
'muzi': muzi,
|
'muzi': muzi,
|
||||||
'zeny': zeny,
|
'zeny': zeny,
|
||||||
'jmena_muzu': utils.histogram([r.osoba.jmeno for r in muzi]),
|
'jmena_muzu': _histogram([r.osoba.jmeno for r in muzi]),
|
||||||
'jmena_zen': utils.histogram([r.osoba.jmeno for r in zeny]),
|
'jmena_zen': _histogram([r.osoba.jmeno for r in zeny]),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -698,11 +210,3 @@ class JakResitView(generic.ListView):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
class AktualniRocnikRedirectView(RedirectView):
|
|
||||||
permanent=False
|
|
||||||
pattern_name = 'seminar_rocnik'
|
|
||||||
|
|
||||||
def get_redirect_url(self, *args, **kwargs):
|
|
||||||
aktualni_rocnik = Nastaveni.get_solo().aktualni_rocnik.rocnik
|
|
||||||
return super().get_redirect_url(rocnik=aktualni_rocnik, *args, **kwargs)
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "seminar/archiv/base.html" %}
|
{% extends "tvorba/archiv/base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load sekizai_tags %}
|
{% load sekizai_tags %}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují:
|
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují.
|
||||||
|
|
||||||
- Zatím tu nic moc není…
|
|
||||||
"""
|
"""
|
||||||
from django.urls import path, re_path
|
from django.urls import path, re_path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,151 @@ import treenode.templatetags as tnltt
|
||||||
import treenode.serializers as vr
|
import treenode.serializers as vr
|
||||||
|
|
||||||
|
|
||||||
|
# ze starého modelu
|
||||||
|
#def verejna_temata(rocnik):
|
||||||
|
# """
|
||||||
|
# Vrací queryset zveřejněných témat v daném ročníku.
|
||||||
|
# """
|
||||||
|
# return Problem.objects.filter(typ=Problem.TYP_TEMA, cislo_zadani__rocnik=rocnik, cislo_zadani__verejne_db=True).order_by('kod')
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: Pozor, níž je ještě jeden ProblemView!
|
||||||
|
#class ProblemView(generic.DetailView):
|
||||||
|
# model = s.Problem
|
||||||
|
# # Zkopírujeme template_name od TreeNodeView, protože jsme prakticky jen trošku upravený TreeNodeView
|
||||||
|
# template_name = TreeNodeView.template_name
|
||||||
|
#
|
||||||
|
# def get_context_data(self, **kwargs):
|
||||||
|
# context = super().get_context_data(**kwargs)
|
||||||
|
# user = self.request.user
|
||||||
|
# # Teď potřebujeme doplnit tnldata do kontextu.
|
||||||
|
# # Ošklivý type switch, hezčí by bylo udělat to polymorfni. FIXME.
|
||||||
|
# if False:
|
||||||
|
# # Hezčí formátování zbytku :-P
|
||||||
|
# pass
|
||||||
|
# elif isinstance(self.object, s.Clanek) or isinstance(self.object, s.Konfera):
|
||||||
|
# # Tyhle Problémy mají ŘešeníNode
|
||||||
|
# context['tnldata'] = TNLData.from_treenode(self.object.reseninode,user)
|
||||||
|
# elif isinstance(self.object, s.Uloha):
|
||||||
|
# # FIXME: Teď vždycky zobrazujeme i vzorák! Možná by bylo hezčí/lepší mít to stejně jako pro Téma: procházet jen dosažitelné z Ročníku / čísla / whatever
|
||||||
|
# tnl_zadani = TNLData.from_treenode(self.object.ulohazadaninode,user)
|
||||||
|
# tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode,user)
|
||||||
|
# context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak])
|
||||||
|
# elif isinstance(self.object, s.Tema):
|
||||||
|
# rocniknode = self.object.rocnik.rocniknode
|
||||||
|
# context['tnldata'] = TNLData.filter_treenode(rocniknode, lambda x: isinstance(x, s.TemaVCisleNode))
|
||||||
|
# else:
|
||||||
|
# raise ValueError("Obecný problém nejde zobrazit.")
|
||||||
|
# return context
|
||||||
|
|
||||||
|
#class AktualniZadaniView(generic.TemplateView):
|
||||||
|
# template_name = 'seminar/treenode.html'
|
||||||
|
|
||||||
|
# TODO Co chceme vlastně zobrazovat na této stránce? Zatím je zde aktuální číslo, ale může tu být cokoli jiného...
|
||||||
|
#class AktualniZadaniView(TreeNodeView):
|
||||||
|
# def get_object(self):
|
||||||
|
# nastaveni = get_object_or_404(Nastaveni)
|
||||||
|
# return nastaveni.aktualni_cislo.cislonode
|
||||||
|
#
|
||||||
|
# def get_context_data(self,**kwargs):
|
||||||
|
# nastaveni = get_object_or_404(Nastaveni)
|
||||||
|
# context = super().get_context_data(**kwargs)
|
||||||
|
# verejne = nastaveni.aktualni_cislo.verejne()
|
||||||
|
# context['verejne'] = verejne
|
||||||
|
# return context
|
||||||
|
|
||||||
|
|
||||||
|
# nastaveni = get_object_or_404(Nastaveni)
|
||||||
|
# temata = verejna_temata(nastaveni.aktualni_rocnik)
|
||||||
|
# for t in temata:
|
||||||
|
# if request.user.is_staff:
|
||||||
|
# t.prispevky = t.prispevek_set.filter(problem=t)
|
||||||
|
# else:
|
||||||
|
# t.prispevky = t.prispevek_set.filter(problem=t, zverejnit=True)
|
||||||
|
# return render(request, 'seminar/zadani/Temata.html',
|
||||||
|
# {
|
||||||
|
# 'temata': temata,
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#def TematkoView(request, rocnik, tematko):
|
||||||
|
# nastaveni = s.Nastaveni.objects.first()
|
||||||
|
# rocnik_object = s.Rocnik.objects.filter(rocnik=rocnik)
|
||||||
|
# tematko_object = s.Tema.objects.filter(rocnik=rocnik_object[0], kod=tematko)
|
||||||
|
# seznam = vytahniZLesaSeznam(tematko_object[0], nastaveni.aktualni_rocnik().rocniknode)
|
||||||
|
# for node, depth in seznam:
|
||||||
|
# if node.isinstance(node, s.KonferaNode):
|
||||||
|
# raise Exception("Not implemented yet")
|
||||||
|
# if node.isinstance(node, s.PohadkaNode): # Mohu ignorovat, má pod sebou
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
# return render(request, 'seminar/tematka/toaletak.html', {})
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#def TemataRozcestnikView(request):
|
||||||
|
# print("=============================================")
|
||||||
|
# nastaveni = s.Nastaveni.objects.first()
|
||||||
|
# tematka_objects = s.Tema.objects.filter(rocnik=nastaveni.aktualni_rocnik())
|
||||||
|
# tematka = [] #List tematka obsahuje pro kazde tematko object a list vsech TemaVCisleNodu - implementované pomocí slovníku
|
||||||
|
# for tematko_object in tematka_objects:
|
||||||
|
# print("AKTUALNI TEMATKO")
|
||||||
|
# print(tematko_object.id)
|
||||||
|
# odkazy = vytahniZLesaSeznam(tematko_object, nastaveni.aktualni_rocnik().rocniknode, pouze_zajimave = True) #Odkazy jsou tuply (node, depth) v listu
|
||||||
|
# print(odkazy)
|
||||||
|
# cisla = [] # List tuplů (nazev cisla, list odkazů)
|
||||||
|
# vcisle = []
|
||||||
|
# cislo = None
|
||||||
|
# for odkaz in odkazy:
|
||||||
|
# if odkaz[1] == 0:
|
||||||
|
# if cislo != None:
|
||||||
|
# cisla.append((cislo, vcisle))
|
||||||
|
# cislo = (odkaz[0].getOdkazStr(), odkaz[0].getOdkaz())
|
||||||
|
# vcisle = []
|
||||||
|
# else:
|
||||||
|
# print(odkaz[0].getOdkaz())
|
||||||
|
# vcisle.append((odkaz[0].getOdkazStr(), odkaz[0].getOdkaz()))
|
||||||
|
# if cislo != None:
|
||||||
|
# cisla.append((cislo, vcisle))
|
||||||
|
#
|
||||||
|
# print(cisla)
|
||||||
|
# tematka.append({
|
||||||
|
# "kod" : tematko_object.kod,
|
||||||
|
# "nazev" : tematko_object.nazev,
|
||||||
|
# "abstrakt" : tematko_object.abstrakt,
|
||||||
|
# "obrazek": tematko_object.obrazek,
|
||||||
|
# "cisla" : cisla
|
||||||
|
# })
|
||||||
|
# return render(request, 'seminar/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik})
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: Pozor, výš je ještě jeden ProblemView!
|
||||||
|
#class ProblemView(generic.DetailView):
|
||||||
|
# model = Problem
|
||||||
|
#
|
||||||
|
# # Používáme funkci, protože přímo template_name neumí mít v přiřazení dost logiky. Ledaže by se to udělalo polymorfně...
|
||||||
|
# def get_template_names(self, **kwargs):
|
||||||
|
# # FIXME: Switch podle typu není hezký, ale nechtělo se mi to přepisovat celé. Správně by se tohle mělo řešit polymorfismem.
|
||||||
|
# spravne_templaty = {
|
||||||
|
# s.Uloha: "uloha",
|
||||||
|
# s.Tema: "tema",
|
||||||
|
# s.Konfera: "konfera",
|
||||||
|
# s.Clanek: "clanek",
|
||||||
|
# }
|
||||||
|
# context = super().get_context_data(**kwargs)
|
||||||
|
# return ['seminar/archiv/problem_' + spravne_templaty[context['object'].__class__] + '.html']
|
||||||
|
#
|
||||||
|
# def get_context_data(self, **kwargs):
|
||||||
|
# context = super().get_context_data(**kwargs)
|
||||||
|
# # Musí se používat context['object'], protože nevíme, jestli dostaneme úložku, téma, článek, .... a tyhle věci vyrábějí různé klíče.
|
||||||
|
# if not context['object'].verejne() and not self.request.user.je_org:
|
||||||
|
# raise PermissionDenied()
|
||||||
|
# if isinstance(context['object'], Clanek):
|
||||||
|
# context['reseni'] = Reseni.objects.filter(problem=context['object']).select_related('resitel').order_by('resitel__prijmeni')
|
||||||
|
# return context
|
||||||
|
|
||||||
class TNLData(object):
|
class TNLData(object):
|
||||||
def __init__(self,anode,parent=None, index=None):
|
def __init__(self,anode,parent=None, index=None):
|
||||||
self.node = anode
|
self.node = anode
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
{% if not prispevek.zverejnit %}<div class="mam-org-only">{% endif %}
|
{% if not prispevek.zverejnit %}<div class="mam-org-only">{% endif %}
|
||||||
<h1>{{prispevek.nazev}}</h1>
|
<h1>{{prispevek.nazev}}</h1>
|
||||||
<h2>{{titul}} {{prispevek.reseni.resitel}}</h2>
|
<h2>{{titul}} {{prispevek.reseni.resitel}}</h2>
|
||||||
<h3>Příspěvek k tématu <a href="..">{{prispevek.problem.nazev}}</a></h3>
|
<h3>Příspěvek k tématu <a href="../../../../seminar/templates/seminar">{{prispevek.problem.nazev}}</a></h3>
|
||||||
{% if prispevek.reseni.body %}
|
{% if prispevek.reseni.body %}
|
||||||
<div class="body">({{prispevek.reseni.body}} b)</div>
|
<div class="body">({{prispevek.reseni.body}} b)</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
0
tvorba/templatetags/__init__.py
Normal file
0
tvorba/templatetags/__init__.py
Normal file
71
tvorba/urls.py
Normal file
71
tvorba/urls.py
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
"""
|
||||||
|
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují.
|
||||||
|
"""
|
||||||
|
from django.urls import path
|
||||||
|
from tvorba import views
|
||||||
|
from personalni.utils import org_required
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
# path('aktualni/temata/', views.TemataRozcestnikView),
|
||||||
|
# path('<int:rocnik>/t<int:tematko>/', views.TematkoView),
|
||||||
|
|
||||||
|
# Archiv
|
||||||
|
path('archiv/rocniky/', views.ArchivView.as_view(), name="seminar_archiv_rocniky"),
|
||||||
|
path('archiv/temata/', views.ArchivTemataView.as_view(), name="seminar_archiv_temata"),
|
||||||
|
|
||||||
|
path('rocnik/<int:rocnik>/', views.RocnikView.as_view(), name='seminar_rocnik'),
|
||||||
|
path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'),
|
||||||
|
# path('problem/<int:pk>/', views.problemView, name='seminar_problem'),
|
||||||
|
|
||||||
|
# Zadani
|
||||||
|
# path('aktualni/zadani/', views.AktualniZadaniView.as_view(), name='seminar_aktualni_zadani'), # Dočasně ad-hoc jednoduchá věc.
|
||||||
|
path('aktualni/zadani/', views.AktualniZadaniView, name='seminar_aktualni_zadani'),
|
||||||
|
# path('aktualni/temata/', views.ZadaniTemataView, name='seminar_temata'),
|
||||||
|
path('aktualni/vysledkova-listina/', views.ZadaniAktualniVysledkovkaView, name='seminar_aktualni_vysledky'),
|
||||||
|
path('aktualni/rocnik/', views.AktualniRocnikRedirectView.as_view(), name='seminar_aktualni_rocnik'),
|
||||||
|
|
||||||
|
# Clanky
|
||||||
|
path('archiv/clanky/', views.ClankyResitelView.as_view(), name='clanky_resitel'),
|
||||||
|
# path('clanky/org/', views.ClankyOrganizatorView.as_view(), name='clanky_organizator'),
|
||||||
|
|
||||||
|
# Stranky viditelne pouze pro orgy:
|
||||||
|
path(
|
||||||
|
'rocnik/<int:rocnik>/vysledkovka.tex',
|
||||||
|
org_required(views.RocnikVysledkovkaView.as_view()),
|
||||||
|
name='seminar_rocnik_vysledkovka'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'rocnik/<int:rocnik>/resitele.csv',
|
||||||
|
org_required(views.resiteleRocnikuCsvExportView),
|
||||||
|
name='seminar_rocnik_resitele_csv'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'rocnik/<int:rocnik>/tituly.tex',
|
||||||
|
org_required(views.TitulyViewRocnik),
|
||||||
|
name='seminar_rocnik_titul'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'rocnik/<int:rocnik>/posledni_vysledkovka.tex',
|
||||||
|
org_required(views.PosledniCisloVysledkovkaView.as_view()),
|
||||||
|
name='seminar_rocnik_posledni_vysledkovka'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'cislo/<int:rocnik>.<str:cislo>/vysledkovka.tex',
|
||||||
|
org_required(views.CisloVysledkovkaView.as_view()),
|
||||||
|
name='seminar_cislo_vysledkovka'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'cislo/<int:rocnik>.<str:cislo>/obalky.pdf',
|
||||||
|
org_required(views.cisloObalkyView),
|
||||||
|
name='seminar_cislo_obalky'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'cislo/<int:rocnik>.<str:cislo>/tituly.tex',
|
||||||
|
org_required(views.TitulyView),
|
||||||
|
name='seminar_cislo_titul'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'cislo/<int:trocnik>.<str:tcislo>/odmeny/<int:frocnik>.<str:fcislo>/',
|
||||||
|
org_required(views.OdmenyView.as_view()),
|
||||||
|
name="seminar_archiv_odmeny"),
|
||||||
|
]
|
||||||
4
tvorba/views/__init__.py
Normal file
4
tvorba/views/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
from aktualni import *
|
||||||
|
from archiv import *
|
||||||
|
from obalky import *
|
||||||
|
from tex import *
|
||||||
68
tvorba/views/aktualni.py
Normal file
68
tvorba/views/aktualni.py
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
from django.shortcuts import get_object_or_404, render
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.views.generic.base import RedirectView
|
||||||
|
|
||||||
|
from tvorba.models import Tema, Nastaveni, Rocnik
|
||||||
|
|
||||||
|
from vysledkovky.utils import VysledkovkaRocniku
|
||||||
|
|
||||||
|
|
||||||
|
def AktualniZadaniView(request):
|
||||||
|
nastaveni = get_object_or_404(Nastaveni)
|
||||||
|
verejne = nastaveni.aktualni_cislo.verejne()
|
||||||
|
return render(
|
||||||
|
request, 'tvorba/zadani/AktualniZadani.html',
|
||||||
|
{
|
||||||
|
'nastaveni': nastaveni,
|
||||||
|
'verejne': verejne,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def ZadaniTemataView(request):
|
||||||
|
nastaveni = get_object_or_404(Nastaveni)
|
||||||
|
verejne = nastaveni.aktualni_cislo.verejne()
|
||||||
|
akt_rocnik = nastaveni.aktualni_cislo.rocnik
|
||||||
|
temata = Tema.objects.filter(rocnik=akt_rocnik, stav='zadany')
|
||||||
|
return render(
|
||||||
|
request, 'tvorba/tematka/rozcestnik.html',
|
||||||
|
{
|
||||||
|
'tematka': temata,
|
||||||
|
'verejne': verejne,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def ZadaniAktualniVysledkovkaView(request):
|
||||||
|
nastaveni = get_object_or_404(Nastaveni)
|
||||||
|
# Aktualni verejna vysledkovka
|
||||||
|
rocnik = nastaveni.aktualni_rocnik
|
||||||
|
context = {'vysledkovka': VysledkovkaRocniku(rocnik, True)}
|
||||||
|
|
||||||
|
# kdyz neni verejna vysledkovka, tak zobraz starou
|
||||||
|
if len(context['vysledkovka'].cisla_rocniku) == 0:
|
||||||
|
try:
|
||||||
|
minuly_rocnik = Rocnik.objects.get(
|
||||||
|
rocnik=(rocnik.rocnik-1))
|
||||||
|
rocnik = minuly_rocnik
|
||||||
|
|
||||||
|
# Přepíšeme prázdnou výsledkovku výsledkovkou z minulého ročníku
|
||||||
|
context['vysledkovka'] = VysledkovkaRocniku(rocnik, True)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
context['rocnik'] = rocnik
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
'tvorba/zadani/AktualniVysledkovka.html',
|
||||||
|
context
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AktualniRocnikRedirectView(RedirectView):
|
||||||
|
permanent = False
|
||||||
|
pattern_name = 'seminar_rocnik'
|
||||||
|
|
||||||
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
|
aktualni_rocnik = Nastaveni.get_solo().aktualni_rocnik.rocnik
|
||||||
|
return super().get_redirect_url(rocnik=aktualni_rocnik, *args, **kwargs)
|
||||||
182
tvorba/views/archiv.py
Normal file
182
tvorba/views/archiv.py
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
import os.path as op
|
||||||
|
from itertools import groupby
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.views import generic
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
from django.http import Http404
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from vysledkovky.utils import VysledkovkaCisla, VysledkovkaRocniku
|
||||||
|
|
||||||
|
from tvorba.models import Rocnik, Cislo, Deadline, Problem, Tema, Clanek
|
||||||
|
from personalni.models.resitel import Resitel
|
||||||
|
|
||||||
|
|
||||||
|
class ArchivView(generic.ListView):
|
||||||
|
model = Rocnik
|
||||||
|
template_name = 'tvorba/archiv/cisla.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(ArchivView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
cisla = Cislo.objects.filter(poradi=1)
|
||||||
|
if not self.request.user.je_org:
|
||||||
|
cisla = cisla.filter(verejne_db=True)
|
||||||
|
urls = {}
|
||||||
|
|
||||||
|
for i, c in enumerate(cisla):
|
||||||
|
# Výchozí nastavení
|
||||||
|
if c.rocnik not in urls:
|
||||||
|
urls[c.rocnik] = op.join(settings.STATIC_URL, "images", "no-picture.png")
|
||||||
|
# NOTE: tohle možná nastavuje poslední titulku
|
||||||
|
if c.titulka_nahled:
|
||||||
|
urls[c.rocnik] = c.titulka_nahled.url
|
||||||
|
|
||||||
|
context["object_list"] = urls
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class RocnikView(generic.DetailView):
|
||||||
|
model = Rocnik
|
||||||
|
template_name = 'tvorba/archiv/rocnik.html'
|
||||||
|
|
||||||
|
# Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik)
|
||||||
|
def get_object(self, queryset=None):
|
||||||
|
if queryset is None:
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
|
||||||
|
return get_object_or_404(queryset, rocnik=self.kwargs.get('rocnik'))
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(RocnikView, self).get_context_data(**kwargs)
|
||||||
|
context["vysledkovka"] = VysledkovkaRocniku(context["rocnik"], True)
|
||||||
|
context["neprazdna_vysledkovka"] = len(context['vysledkovka'].cisla_rocniku) != 0
|
||||||
|
context["vysledkovka_neverejna"] = VysledkovkaRocniku(context["rocnik"], False)
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
def resiteleRocnikuCsvExportView(request, rocnik):
|
||||||
|
from personalni.views import dataResiteluCsvResponse
|
||||||
|
assert request.method in ('GET', 'HEAD')
|
||||||
|
return dataResiteluCsvResponse(
|
||||||
|
Resitel.objects.resi_v_rocniku(
|
||||||
|
get_object_or_404(Rocnik, rocnik=rocnik)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CisloView(generic.DetailView):
|
||||||
|
# FIXME zobrazování témátek a vůbec, teď je tam jen odkaz na číslo v pdf
|
||||||
|
model = Cislo
|
||||||
|
template_name = 'tvorba/archiv/cislo.html'
|
||||||
|
|
||||||
|
# Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik)
|
||||||
|
def get_object(self, queryset=None):
|
||||||
|
if queryset is None:
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
rocnik_arg = self.kwargs.get('rocnik')
|
||||||
|
poradi_arg = self.kwargs.get('cislo')
|
||||||
|
queryset = queryset.filter(rocnik__rocnik=rocnik_arg, poradi=poradi_arg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj = queryset.get()
|
||||||
|
except queryset.model.DoesNotExist:
|
||||||
|
raise Http404(
|
||||||
|
_("No %(verbose_name)s found matching the query") %
|
||||||
|
{'verbose_name': queryset.model._meta.verbose_name}
|
||||||
|
)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(CisloView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
cislo = context['cislo']
|
||||||
|
context['prevcislo'] = Cislo.objects.filter(
|
||||||
|
(Q(rocnik__lt=self.object.rocnik) | Q(poradi__lt=self.object.poradi)) &
|
||||||
|
Q(rocnik__lte=self.object.rocnik)
|
||||||
|
).first()
|
||||||
|
|
||||||
|
deadliny = Deadline.objects.filter(cislo=cislo).reverse()
|
||||||
|
deadliny_s_vysledkovkami = []
|
||||||
|
|
||||||
|
nadpisy = {
|
||||||
|
Deadline.TYP_CISLA: "Výsledkovka",
|
||||||
|
Deadline.TYP_PRVNI: "Výsledkovka do prvního deadlinu",
|
||||||
|
Deadline.TYP_PRVNI_A_SOUS: "Výsledkovka do prvního deadlinu a deadlinu pro účast na soustředění",
|
||||||
|
Deadline.TYP_SOUS: "Výsledkovka do deadlinu pro účast na soustředění",
|
||||||
|
}
|
||||||
|
|
||||||
|
for deadline in deadliny:
|
||||||
|
if self.request.user.je_org | deadline.verejna_vysledkovka:
|
||||||
|
deadliny_s_vysledkovkami.append((
|
||||||
|
deadline, nadpisy[deadline.typ],
|
||||||
|
VysledkovkaCisla(
|
||||||
|
cislo, not self.request.user.je_org, deadline
|
||||||
|
),
|
||||||
|
))
|
||||||
|
|
||||||
|
context['deadliny_s_vysledkovkami'] = deadliny_s_vysledkovkami
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class ArchivTemataView(generic.ListView):
|
||||||
|
model = Problem
|
||||||
|
template_name = 'tvorba/archiv/temata.html'
|
||||||
|
queryset = Tema.objects.filter(stav=Problem.STAV_ZADANY).select_related('rocnik').order_by('rocnik', 'kod')
|
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs):
|
||||||
|
ctx = super().get_context_data(*args, **kwargs)
|
||||||
|
ctx['rocniky'] = OrderedDict()
|
||||||
|
for rocnik, temata in groupby(ctx['object_list'], lambda tema: tema.rocnik):
|
||||||
|
ctx['rocniky'][rocnik] = list(temata)
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
### Články
|
||||||
|
def group_by_rocnik(clanky):
|
||||||
|
""" Vezme zadaný seznam článků a seskupí je podle ročníku.
|
||||||
|
Vrátí seznam seznamů článků ze stejného ročníku."""
|
||||||
|
if len(clanky) == 0:
|
||||||
|
return clanky
|
||||||
|
clanky.order_by('cislo__rocnik__rocnik')
|
||||||
|
skupiny_clanku = []
|
||||||
|
skupina = []
|
||||||
|
|
||||||
|
rocnik = clanky.first().cislo.rocnik.rocnik # první ročník
|
||||||
|
for clanek in clanky:
|
||||||
|
if clanek.cislo.rocnik.rocnik == rocnik:
|
||||||
|
skupina.append(clanek)
|
||||||
|
else:
|
||||||
|
skupiny_clanku.append(skupina)
|
||||||
|
skupina = [clanek]
|
||||||
|
rocnik = clanek.cislo.rocnik.rocnik
|
||||||
|
skupiny_clanku.append(skupina)
|
||||||
|
return skupiny_clanku
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: clanky jsou vsechny, pokud budou i neresitelske, tak se take zobrazi
|
||||||
|
# FIXME: Původně tu byl kód přímo v těle třídy, což rozbíjelo migrace. Opravil jsem, ale vůbec nevím, jestli to funguje.
|
||||||
|
class ClankyResitelView(generic.ListView):
|
||||||
|
model = Problem
|
||||||
|
template_name = 'tvorba/clanky/resitelske_clanky.html'
|
||||||
|
|
||||||
|
# FIXME: QuerySet není pole!
|
||||||
|
def get_queryset(self):
|
||||||
|
clanky = Clanek.objects.filter(stav=Problem.STAV_VYRESENY).select_related('cislo__rocnik').order_by('-cislo__rocnik__rocnik')
|
||||||
|
queryset = []
|
||||||
|
skupiny_clanku = group_by_rocnik(clanky)
|
||||||
|
for skupina in skupiny_clanku:
|
||||||
|
skupina.sort(key=lambda clanek: clanek.kod_v_rocniku)
|
||||||
|
for clanek in skupina:
|
||||||
|
queryset.append(clanek)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
# FIXME: pokud chceme orgoclanky, tak nejak zavest do modelu a podle toho odkomentovat a upravit
|
||||||
|
# class ClankyOrganizatorView(generic.ListView):
|
||||||
|
# model = Problem
|
||||||
|
# template_name = 'seminar/clanky/organizatorske_clanky.html'
|
||||||
|
# queryset = Problem.objects.filter(stav=Problem.STAV_ZADANY).select_related('cislo_zadani__rocnik').order_by('-cislo_zadani__rocnik__rocnik', 'kod')
|
||||||
91
tvorba/views/obalky.py
Normal file
91
tvorba/views/obalky.py
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
import tempfile
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.shortcuts import get_object_or_404, render
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.views import generic
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from tvorba.models import Cislo, Deadline
|
||||||
|
from personalni.models.resitel import Resitel
|
||||||
|
from vysledkovky.utils import body_resitelu
|
||||||
|
|
||||||
|
|
||||||
|
def cisloObalkyView(request, rocnik, cislo):
|
||||||
|
realne_cislo = get_object_or_404(Cislo, poradi=cislo, rocnik__rocnik=rocnik)
|
||||||
|
return obalkyView(request, Resitel.objects.aktivni_resitele(realne_cislo))
|
||||||
|
|
||||||
|
|
||||||
|
def obalkyView(request, resitele):
|
||||||
|
tex = render(
|
||||||
|
request, 'tvorba/archiv/obalky.tex',
|
||||||
|
{'resitele': resitele}
|
||||||
|
).content
|
||||||
|
|
||||||
|
tempdir = tempfile.mkdtemp()
|
||||||
|
with open(tempdir+"/obalky.tex", "w") as texfile:
|
||||||
|
texfile.write(tex.decode())
|
||||||
|
shutil.copy(os.path.join(settings.STATIC_ROOT, 'seminar/lisak.pdf'), tempdir)
|
||||||
|
subprocess.call(["pdflatex", "obalky.tex"], cwd=tempdir)
|
||||||
|
|
||||||
|
with open(tempdir+"/obalky.pdf", "rb") as pdffile:
|
||||||
|
response = HttpResponse(pdffile.read(), content_type='application/pdf')
|
||||||
|
shutil.rmtree(tempdir)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class OdmenyView(generic.TemplateView):
|
||||||
|
template_name = 'tvorba/archiv/odmeny.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
fromcislo = get_object_or_404(
|
||||||
|
Cislo,
|
||||||
|
rocnik=self.kwargs.get('frocnik'),
|
||||||
|
poradi=self.kwargs.get('fcislo'),
|
||||||
|
)
|
||||||
|
tocislo = get_object_or_404(
|
||||||
|
Cislo,
|
||||||
|
rocnik=self.kwargs.get('trocnik'),
|
||||||
|
poradi=self.kwargs.get('tcislo'),
|
||||||
|
)
|
||||||
|
resitele = Resitel.objects.aktivni_resitele(tocislo)
|
||||||
|
|
||||||
|
def get_diff(from_deadline: Deadline, to_deadline: Deadline):
|
||||||
|
frombody = body_resitelu(resitele=resitele, jen_verejne=False, do=from_deadline)
|
||||||
|
tobody = body_resitelu(resitele=resitele, jen_verejne=False, do=to_deadline)
|
||||||
|
outlist = []
|
||||||
|
for resitel in resitele:
|
||||||
|
fbody = frombody.get(resitel.id, 0)
|
||||||
|
tbody = tobody.get(resitel.id, 0)
|
||||||
|
ftitul = resitel.get_titul(fbody)
|
||||||
|
ttitul = resitel.get_titul(tbody)
|
||||||
|
if ftitul != ttitul:
|
||||||
|
outlist.append({
|
||||||
|
'jmeno': resitel.osoba.plne_jmeno(),
|
||||||
|
'ftitul': ftitul,
|
||||||
|
'ttitul': ttitul},
|
||||||
|
)
|
||||||
|
return outlist
|
||||||
|
|
||||||
|
def posledni_deadline_oprava(cislo: Cislo) -> Deadline:
|
||||||
|
posledni_deadline = cislo.posledni_deadline
|
||||||
|
if posledni_deadline is None:
|
||||||
|
return Deadline.objects.filter(
|
||||||
|
Q(cislo__poradi__lt=cislo.poradi, cislo__rocnik=cislo.rocnik) |
|
||||||
|
Q(cislo__rocnik__rocnik__lt=cislo.rocnik.rocnik),
|
||||||
|
).order_by("deadline").last()
|
||||||
|
return posledni_deadline
|
||||||
|
|
||||||
|
context["from_cislo"] = fromcislo
|
||||||
|
context["to_cislo"] = tocislo
|
||||||
|
from_deadline = posledni_deadline_oprava(fromcislo)
|
||||||
|
to_deadline = posledni_deadline_oprava(tocislo)
|
||||||
|
context["from_deadline"] = from_deadline
|
||||||
|
context["to_deadline"] = to_deadline
|
||||||
|
context["zmeny"] = get_diff(from_deadline, to_deadline)
|
||||||
|
|
||||||
|
return context
|
||||||
132
tvorba/views/tex.py
Normal file
132
tvorba/views/tex.py
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
import unicodedata
|
||||||
|
|
||||||
|
from django.shortcuts import get_object_or_404, render
|
||||||
|
from django.views import generic
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
from django.http import Http404
|
||||||
|
|
||||||
|
from personalni.models.resitel import Resitel
|
||||||
|
from tvorba.models import Rocnik, Cislo, Deadline
|
||||||
|
|
||||||
|
from vysledkovky.utils import body_resitelu, VysledkovkaCisla, VysledkovkaDoTeXu
|
||||||
|
from tvorba.views.archiv import CisloView, RocnikView
|
||||||
|
|
||||||
|
|
||||||
|
class CisloVysledkovkaView(CisloView):
|
||||||
|
"""View vytvořené pro stránku zobrazující výsledkovku čísla v TeXu."""
|
||||||
|
|
||||||
|
model = Cislo
|
||||||
|
template_name = 'tvorba/archiv/cislo_vysledkovka.tex'
|
||||||
|
# content_type = 'application/x-tex; charset=UTF8'
|
||||||
|
# umozni rovnou stahnout TeXovsky dokument
|
||||||
|
content_type = 'text/plain; charset=UTF8'
|
||||||
|
# vypise na stranku textovy obsah vyTeXane vysledkovky k okopirovani
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(CisloVysledkovkaView, self).get_context_data()
|
||||||
|
cislo = context['cislo']
|
||||||
|
|
||||||
|
cislopred = cislo.predchozi()
|
||||||
|
if cislopred is not None:
|
||||||
|
context['vysledkovka'] = VysledkovkaDoTeXu(
|
||||||
|
cislo,
|
||||||
|
od_vyjma=cislopred.zlomovy_deadline_pro_papirove_cislo(),
|
||||||
|
do_vcetne=cislo.zlomovy_deadline_pro_papirove_cislo(),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
context['vysledkovka'] = VysledkovkaCisla(
|
||||||
|
cislo,
|
||||||
|
jen_verejne=False,
|
||||||
|
do_deadlinu=cislo.zlomovy_deadline_pro_papirove_cislo(),
|
||||||
|
)
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
# Podle předchozího
|
||||||
|
class PosledniCisloVysledkovkaView(generic.DetailView):
|
||||||
|
"""View vytvořené pro zobrazení výsledkovky posledního čísla v TeXu."""
|
||||||
|
|
||||||
|
model = Rocnik
|
||||||
|
template_name = 'tvorba/archiv/cislo_vysledkovka.tex'
|
||||||
|
content_type = 'text/plain; charset=UTF8'
|
||||||
|
|
||||||
|
def get_object(self, queryset=None):
|
||||||
|
if queryset is None:
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
rocnik_arg = self.kwargs.get('rocnik')
|
||||||
|
queryset = queryset.filter(rocnik=rocnik_arg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj = queryset.get()
|
||||||
|
except queryset.model.DoesNotExist:
|
||||||
|
raise Http404(
|
||||||
|
_("No %(verbose_name)s found matching the query") %
|
||||||
|
{'verbose_name': queryset.model._meta.verbose_name}
|
||||||
|
)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(PosledniCisloVysledkovkaView, self).get_context_data()
|
||||||
|
rocnik = context['rocnik']
|
||||||
|
cislo = rocnik.cisla.order_by("poradi").last()
|
||||||
|
cislopred = cislo.predchozi()
|
||||||
|
context['vysledkovka'] = VysledkovkaDoTeXu(
|
||||||
|
cislo,
|
||||||
|
od_vyjma=cislopred.zlomovy_deadline_pro_papirove_cislo(),
|
||||||
|
do_vcetne=cislo.deadline_v_cisle.order_by("deadline").last(),
|
||||||
|
)
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class RocnikVysledkovkaView(RocnikView):
|
||||||
|
""" View vytvořené pro stránku zobrazující výsledkovku ročníku v TeXu."""
|
||||||
|
model = Rocnik
|
||||||
|
template_name = 'tvorba/archiv/rocnik_vysledkovka.tex'
|
||||||
|
# content_type = 'application/x-tex; charset=UTF8'
|
||||||
|
# umozni rovnou stahnout TeXovsky dokument
|
||||||
|
content_type = 'text/plain; charset=UTF8'
|
||||||
|
# vypise na stranku textovy obsah vyTeXane vysledkovky k okopirovani
|
||||||
|
|
||||||
|
|
||||||
|
# Tituly
|
||||||
|
def TitulyViewRocnik(request, rocnik):
|
||||||
|
return TitulyView(request, rocnik, None)
|
||||||
|
|
||||||
|
|
||||||
|
def TitulyView(request, rocnik, cislo):
|
||||||
|
""" View pro stažení makra titulů v TeXu."""
|
||||||
|
rocnik_obj = get_object_or_404(Rocnik, rocnik=rocnik)
|
||||||
|
resitele = Resitel.objects.filter(rok_maturity__gte=rocnik_obj.prvni_rok)
|
||||||
|
|
||||||
|
asciijmena = []
|
||||||
|
jmenovci = False # detekuje, zda jsou dva řešitelé jmenovci (modulo nabodeníčka),
|
||||||
|
# pokud ano, vrátí se jako true
|
||||||
|
if cislo is not None:
|
||||||
|
cislo_obj = get_object_or_404(Cislo, rocnik=rocnik_obj, poradi=cislo)
|
||||||
|
slovnik_s_body = body_resitelu(
|
||||||
|
do=cislo_obj.zlomovy_deadline_pro_papirove_cislo(), jen_verejne=False
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
slovnik_s_body = body_resitelu(
|
||||||
|
do=Deadline.objects.filter(cislo__rocnik=rocnik_obj).last(),
|
||||||
|
jen_verejne=False
|
||||||
|
)
|
||||||
|
|
||||||
|
for resitel in resitele:
|
||||||
|
resitel.titul = resitel.get_titul(slovnik_s_body[resitel.id])
|
||||||
|
jmeno = resitel.osoba.jmeno+resitel.osoba.prijmeni
|
||||||
|
# převedeme jména a příjmení řešitelů do ASCII
|
||||||
|
ascii_jmeno_bytes = unicodedata.normalize('NFKD', jmeno).encode("ascii", "ignore")
|
||||||
|
# vrátí se byte string, převedeme na standardní string
|
||||||
|
ascii_jmeno_divnoznaky = str(ascii_jmeno_bytes, "utf-8", "ignore").replace(" ", "")
|
||||||
|
resitel.ascii = ''.join(a for a in ascii_jmeno_divnoznaky if a.isalnum())
|
||||||
|
if resitel.ascii not in asciijmena:
|
||||||
|
asciijmena.append(resitel.ascii)
|
||||||
|
else:
|
||||||
|
jmenovci = True
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request, 'tvorba/archiv/tituly.tex',
|
||||||
|
{'resitele': resitele, 'jmenovci': jmenovci},
|
||||||
|
content_type="text/plain"
|
||||||
|
)
|
||||||
|
|
@ -1,15 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují:
|
Soubor sloužící jako „router“, tj. zde se definují url adresy a na co ukazují.
|
||||||
|
|
||||||
- ``prihlasit/`` (login) :class:`~various.autentizace.views.LoginView`
|
|
||||||
- ``login/`` :class:`django.views.generic.base.RedirectView` předchozího
|
|
||||||
- ``odhlasit/`` (logout) :class:`~various.autentizace.views.LogoutView`
|
|
||||||
- ``logout/`` :class:`django.views.generic.base.RedirectView` předchozího
|
|
||||||
- ``reset-hesla/`` (reset_password) :class:`~various.autentizace.views.PasswordResetView`
|
|
||||||
- ``zmena-hesla/`` (change_password) :class:`~various.autentizace.views.PasswordChangeView`
|
|
||||||
- ``zmena-hesla/2/`` (reset_password_done) :class:`~various.autentizace.views.PasswordResetDoneView`
|
|
||||||
- ``reset-hesla/potvrzeni/<uidb64>/<token>/`` (reset_password_confirm) :class:`~various.autentizace.views.PasswordResetConfirmView`
|
|
||||||
- ``reset-hesla/hotovo/`` (reset_password_complete) :class:`~various.autentizace.views.PasswordResetCompleteView`
|
|
||||||
"""
|
"""
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue