Merge branch 'data_migrations' of gimli.ms.mff.cuni.cz:/akce/mam/git/mamweb into data_migrations
This commit is contained in:
commit
f960853239
26 changed files with 382 additions and 64 deletions
|
@ -695,13 +695,13 @@
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"description": "",
|
"description": "",
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"hint": "",
|
"hint": "To, co ŘEŠITELÉ poslali",
|
||||||
"inbreadcrumbs": true,
|
"inbreadcrumbs": true,
|
||||||
"inmenu": true,
|
"inmenu": true,
|
||||||
"insitetree": true,
|
"insitetree": true,
|
||||||
"parent": 21,
|
"parent": 21,
|
||||||
"sort_order": 37,
|
"sort_order": 38,
|
||||||
"title": "Odevzdaná řešení",
|
"title": "Došlá řešení",
|
||||||
"tree": 1,
|
"tree": 1,
|
||||||
"url": "odevzdavatko_tabulka",
|
"url": "odevzdavatko_tabulka",
|
||||||
"urlaspattern": true
|
"urlaspattern": true
|
||||||
|
@ -724,7 +724,7 @@
|
||||||
"inmenu": true,
|
"inmenu": true,
|
||||||
"insitetree": true,
|
"insitetree": true,
|
||||||
"parent": 21,
|
"parent": 21,
|
||||||
"sort_order": 38,
|
"sort_order": 42,
|
||||||
"title": "Odhlásit se",
|
"title": "Odhlásit se",
|
||||||
"tree": 1,
|
"tree": 1,
|
||||||
"url": "logout",
|
"url": "logout",
|
||||||
|
@ -806,5 +806,31 @@
|
||||||
},
|
},
|
||||||
"model": "sitetree.treeitem",
|
"model": "sitetree.treeitem",
|
||||||
"pk": 41
|
"pk": 41
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"access_guest": false,
|
||||||
|
"access_loggedin": false,
|
||||||
|
"access_perm_type": 1,
|
||||||
|
"access_permissions": [
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"access_restricted": true,
|
||||||
|
"alias": null,
|
||||||
|
"description": "",
|
||||||
|
"hidden": false,
|
||||||
|
"hint": "To, co jsem JÁ odevzdal",
|
||||||
|
"inbreadcrumbs": true,
|
||||||
|
"inmenu": true,
|
||||||
|
"insitetree": true,
|
||||||
|
"parent": 21,
|
||||||
|
"sort_order": 37,
|
||||||
|
"title": "Odevzdaná řešení",
|
||||||
|
"tree": 1,
|
||||||
|
"url": "seminar_resitel_odevzdana_reseni",
|
||||||
|
"urlaspattern": true
|
||||||
|
},
|
||||||
|
"model": "sitetree.treeitem",
|
||||||
|
"pk": 42
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -78,6 +78,7 @@ TEMPLATES = [
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
'sekizai.context_processors.sekizai',
|
'sekizai.context_processors.sekizai',
|
||||||
'header_fotky.context_processors.vzhled',
|
'header_fotky.context_processors.vzhled',
|
||||||
|
'various.context_processors.april',
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -132,6 +133,7 @@ INSTALLED_APPS = (
|
||||||
'korektury',
|
'korektury',
|
||||||
'prednasky',
|
'prednasky',
|
||||||
'header_fotky',
|
'header_fotky',
|
||||||
|
'various',
|
||||||
|
|
||||||
# Admin upravy:
|
# Admin upravy:
|
||||||
|
|
||||||
|
|
|
@ -131,6 +131,48 @@
|
||||||
$("a[rel^='gallery-image']").prettyPhoto(prettyparams);
|
$("a[rel^='gallery-image']").prettyPhoto(prettyparams);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
{% if april == 2021 %}
|
||||||
|
<script type="text/javascript" charset="utf-8">
|
||||||
|
function rotace(vektor, uhel_deg) {
|
||||||
|
var uhel = uhel_deg *(Math.PI / 180);
|
||||||
|
var x = vektor[0];
|
||||||
|
var y = vektor[1];
|
||||||
|
return [x*Math.cos(uhel) - y*Math.sin(uhel), x*Math.sin(uhel) + y*Math.cos(uhel)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotace_a_posun(obj, uhel) {
|
||||||
|
var ow = obj.width();
|
||||||
|
var oh = obj.height();
|
||||||
|
|
||||||
|
var rohy = [[0,0], [0,oh], [ow, 0], [ow, oh]];
|
||||||
|
var minx = 0;
|
||||||
|
var miny = 0;
|
||||||
|
for (var roh of rohy) {
|
||||||
|
var otoceny = rotace(roh, uhel);
|
||||||
|
if (otoceny[0] < minx) {
|
||||||
|
minx = otoceny[0];
|
||||||
|
}
|
||||||
|
if (otoceny[1] < miny) {
|
||||||
|
miny = otoceny[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
miny *= -1;
|
||||||
|
minx *= -1;
|
||||||
|
|
||||||
|
var transf_str = "translateX("+minx+"px) translateY("+miny+"px) rotate("+uhel+"deg)";
|
||||||
|
obj.css('transform-origin', 'top left');
|
||||||
|
obj.css('transform', transf_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomUhel() {
|
||||||
|
return Math.floor(360*Math.random());
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.container').css('margin', 0);
|
||||||
|
rotace_a_posun($('.container'), randomUhel());
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
{% render_block "js" %}
|
{% render_block "js" %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -142,6 +142,28 @@ class PrihlaskaForm(forms.Form):
|
||||||
elif data.get('skola_adresa')=='':
|
elif data.get('skola_adresa')=='':
|
||||||
self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy'))
|
self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy'))
|
||||||
|
|
||||||
|
# Editační formulář bez řešitele.
|
||||||
|
class ProfileEditFormPoMaturite(forms.Form):
|
||||||
|
username = forms.CharField(label='Přihlašovací jméno',
|
||||||
|
max_length=256,
|
||||||
|
required=True)
|
||||||
|
|
||||||
|
jmeno = forms.CharField(label='Jméno', max_length=256, required=True)
|
||||||
|
prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True)
|
||||||
|
pohlavi_muz = forms.ChoiceField(label='Pohlaví',
|
||||||
|
choices = ((True,'muž'),(False,'žena')), required=True)
|
||||||
|
email = forms.EmailField(label='E-mail',max_length=256, required=True)
|
||||||
|
telefon = forms.CharField(widget=TelInput(),label='Telefon',max_length=256, required=False)
|
||||||
|
datum_narozeni = forms.DateField(widget=DateInput(),label='Datum narození', required=False)
|
||||||
|
ulice = forms.CharField(label='Ulice', max_length=256, required=False)
|
||||||
|
mesto = forms.CharField(label='Město', max_length=256, required=False)
|
||||||
|
psc = forms.CharField(label='PSČ', max_length=32, required=False)
|
||||||
|
stat = forms.ChoiceField(label='Stát',
|
||||||
|
choices = (('CZ', 'Česká Republika'),
|
||||||
|
('SK', 'Slovenská Republika'),
|
||||||
|
('other', 'Jiné')),
|
||||||
|
required=False)
|
||||||
|
stat_text = forms.CharField(label='Stát', max_length=256, required=False)
|
||||||
|
|
||||||
class ProfileEditForm(forms.Form):
|
class ProfileEditForm(forms.Form):
|
||||||
username = forms.CharField(label='Přihlašovací jméno',
|
username = forms.CharField(label='Přihlašovací jméno',
|
||||||
|
@ -181,7 +203,7 @@ class ProfileEditForm(forms.Form):
|
||||||
|
|
||||||
rok_maturity = forms.IntegerField(
|
rok_maturity = forms.IntegerField(
|
||||||
label='Rok maturity',
|
label='Rok maturity',
|
||||||
min_value=date.today().year,
|
min_value=date.today().year,
|
||||||
max_value=date.today().year+8,
|
max_value=date.today().year+8,
|
||||||
required=True)
|
required=True)
|
||||||
zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True)
|
zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True)
|
||||||
|
|
|
@ -107,22 +107,50 @@ def spoj_k_organizatorum_osoby(apps, scema_editor):
|
||||||
for org in Organizator.objects.all():
|
for org in Organizator.objects.all():
|
||||||
|
|
||||||
# Spárování organizátora s osobou
|
# Spárování organizátora s osobou
|
||||||
|
# Myšlenka: Když najdeme řešitele pro daného uživatele, tak se vezme Osoba příslušná uživateli,
|
||||||
|
# Pokud nenajdeme uživatele, tak ještě zkusíme dohledat Osobu podle e-mailu
|
||||||
user = org.user
|
user = org.user
|
||||||
resitele = Resitel.objects.filter(user=user)
|
if user is None:
|
||||||
if resitele.count() != 0:
|
logger.error(f'Org {org} nemá uživatele!')
|
||||||
osoba = resitele.first().osoba
|
# Je to podezřelé, ale prostě vyrobíme novou osobu.
|
||||||
else:
|
|
||||||
osoba = Osoba(user=user)
|
osoba = Osoba(user=user)
|
||||||
|
# Téhle osobě nejdou nastavit detaily, protože žádné nemáme.
|
||||||
|
else:
|
||||||
|
logger.info(f'Org {org.user.email}(ID: {org.id}) má uživatele {user}')
|
||||||
|
# 💢💢💢 Python nemá goto, ale prý má výjimky… 💢💢💢
|
||||||
|
class EndException(Exception): pass
|
||||||
|
try:
|
||||||
|
# Hledáme podle uživatele
|
||||||
|
resitele = Resitel.objects.filter(user=user)
|
||||||
|
if resitele.count() != 0 and user is not None:
|
||||||
|
osoba = resitele.first().osoba
|
||||||
|
logger.info(f'Našel jsem řešitele {resitele.first().email} podle uživatele, používám jeho Osobu')
|
||||||
|
raise EndException
|
||||||
|
|
||||||
|
# Hledáme podle e-mailu
|
||||||
|
osoby = Osoba.objects.filter(email__iexact=user.email)
|
||||||
|
if osoby.count() != 0 and user.email != '':
|
||||||
|
osoba = osoby.first()
|
||||||
|
if osoba.user is None:
|
||||||
|
osoba.user = user
|
||||||
|
logger.info(f'Našel jsem Osobu {osoby.first().email} podle e-mailu')
|
||||||
|
raise EndException
|
||||||
|
|
||||||
# Přesun informací z usera do osoby
|
# Fallback
|
||||||
# pro řešitele již v minule migraci
|
logger.warning(f'Org neměl řešitele, zakládám novou Osobu.')
|
||||||
osoba.jmeno = user.first_name
|
osoba = Osoba(user=user)
|
||||||
osoba.prijmeni = user.last_name
|
|
||||||
osoba.email = user.email
|
# Přesun informací z usera do osoby
|
||||||
user.jmeno = "Použij osobu!"
|
# pro osoby z řešitelů (jediné dosud existující osoby) již v minule migraci
|
||||||
user.prijmeni = "Použij osobu!"
|
osoba.jmeno = user.first_name
|
||||||
user.email = "Použij osobu!"
|
osoba.prijmeni = user.last_name
|
||||||
user.save()
|
osoba.email = user.email
|
||||||
|
user.jmeno = "Použij osobu!"
|
||||||
|
user.prijmeni = "Použij osobu!"
|
||||||
|
user.email = "Použij osobu!"
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
except EndException: pass
|
||||||
|
|
||||||
# Přesun informací z organizátora do jeho osoby
|
# Přesun informací z organizátora do jeho osoby
|
||||||
osoba.prezdivka = org.prezdivka if org.prezdivka is not None else ''
|
osoba.prezdivka = org.prezdivka if org.prezdivka is not None else ''
|
||||||
|
@ -142,7 +170,10 @@ def fix_problem(apps, schema_editor):
|
||||||
else:
|
else:
|
||||||
pr.autor = None
|
pr.autor = None
|
||||||
if pr.opravovatel is not None:
|
if pr.opravovatel is not None:
|
||||||
pr.opravovatele.add(Organizator.objects.filter(osoba__user=pr.opravovatel).first())
|
if Organizator.objects.filter(osoba__user=pr.opravovatel).first() is not None:
|
||||||
|
pr.opravovatele.add(Organizator.objects.filter(osoba__user=pr.opravovatel).first())
|
||||||
|
else:
|
||||||
|
logger.error(f'WTF, nespárovaný opravovatel {pr.opravovatel} problému {pr}')
|
||||||
pr.save()
|
pr.save()
|
||||||
|
|
||||||
def fix_pohadka(apps, schema_editor):
|
def fix_pohadka(apps, schema_editor):
|
||||||
|
|
|
@ -28,26 +28,23 @@ def resitel_to_osoba(apps,schema_editor):
|
||||||
if u.first_name:
|
if u.first_name:
|
||||||
if not o.jmeno:
|
if not o.jmeno:
|
||||||
o.jmeno = u.first_name
|
o.jmeno = u.first_name
|
||||||
u.first_name = 'Použij osobu!'
|
u.first_name += ' (Uživatel!)'
|
||||||
elif o.jmeno == u.first_name:
|
elif o.jmeno == u.first_name:
|
||||||
u.first_name = 'Použij osobu!'
|
u.first_name += ' (Uživatel!)'
|
||||||
else:
|
else:
|
||||||
raise ValueError('jmeno a first_name rozdílné: "{}" vs. "{}"'.format(o.jmeno, u.first_name))
|
raise ValueError('jmeno a first_name rozdílné: "{}" vs. "{}"'.format(o.jmeno, u.first_name))
|
||||||
if u.last_name:
|
if u.last_name:
|
||||||
if not o.prijmeni:
|
if not o.prijmeni:
|
||||||
o.prijmeni = u.last_name
|
o.prijmeni = u.last_name
|
||||||
u.last_name = 'Použij osobu!'
|
u.last_name += ' (Uživatel!)'
|
||||||
elif o.prijmeni == u.last_name:
|
elif o.prijmeni == u.last_name:
|
||||||
u.last_name = 'Použij osobu!'
|
u.last_name += ' (Uživatel!)'
|
||||||
else:
|
else:
|
||||||
raise ValueError('prijmeni a last_name rozdílné: "{}" vs. "{}"'.format(o.prijmeni, u.last_name))
|
raise ValueError('prijmeni a last_name rozdílné: "{}" vs. "{}"'.format(o.prijmeni, u.last_name))
|
||||||
if u.email:
|
if u.email:
|
||||||
if not o.email:
|
if not o.email:
|
||||||
o.email = u.email
|
o.email = u.email
|
||||||
u.email = 'Použij osobu!'
|
elif o.email != u.email:
|
||||||
elif o.email == u.email:
|
|
||||||
u.email = 'Použij osobu!'
|
|
||||||
else:
|
|
||||||
raise ValueError('o.email a u.email rozdílné: "{}" vs. "{}"'.format(o.email, u.email))
|
raise ValueError('o.email a u.email rozdílné: "{}" vs. "{}"'.format(o.email, u.email))
|
||||||
u.save()
|
u.save()
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,10 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def spoj_k_organizatorum_osoby(apps, scema_editor):
|
def spoj_k_organizatorum_osoby(apps, scema_editor):
|
||||||
Organizator = apps.get_model('seminar', 'Organizator')
|
Organizator = apps.get_model('seminar', 'Organizator')
|
||||||
Resitel = apps.get_model('seminar', 'Resitel')
|
Resitel = apps.get_model('seminar', 'Resitel')
|
||||||
|
@ -11,22 +15,49 @@ def spoj_k_organizatorum_osoby(apps, scema_editor):
|
||||||
for org in Organizator.objects.all():
|
for org in Organizator.objects.all():
|
||||||
|
|
||||||
# Spárování organizátora s osobou
|
# Spárování organizátora s osobou
|
||||||
|
# Myšlenka: Když najdeme řešitele pro daného uživatele, tak se vezme Osoba příslušná uživateli,
|
||||||
|
# Pokud nenajdeme uživatele, tak ještě zkusíme dohledat Osobu podle e-mailu
|
||||||
user = org.user
|
user = org.user
|
||||||
resitele = Resitel.objects.filter(user=user)
|
if user is None:
|
||||||
if resitele.count() != 0:
|
logger.error(f'Org {org} nemá uživatele!')
|
||||||
osoba = resitele.first().osoba
|
# Je to podezřelé, ale prostě vyrobíme novou osobu.
|
||||||
else:
|
|
||||||
osoba = Osoba(user=user)
|
osoba = Osoba(user=user)
|
||||||
|
# Téhle osobě nejdou nastavit detaily, protože žádné nemáme.
|
||||||
|
else:
|
||||||
|
logger.info(f'Org {org.user.email}(ID: {org.id}) má uživatele {user}')
|
||||||
|
# 💢💢💢 Python nemá goto, ale prý má výjimky… 💢💢💢
|
||||||
|
class EndException(Exception): pass
|
||||||
|
try:
|
||||||
|
# Hledáme podle uživatele
|
||||||
|
resitele = Resitel.objects.filter(user=user)
|
||||||
|
if resitele.count() != 0 and user is not None:
|
||||||
|
osoba = resitele.first().osoba
|
||||||
|
logger.info(f'Našel jsem řešitele {resitele.first().email} podle uživatele, používám jeho Osobu')
|
||||||
|
raise EndException
|
||||||
|
|
||||||
|
# Hledáme podle e-mailu
|
||||||
|
osoby = Osoba.objects.filter(email__iexact=user.email)
|
||||||
|
if osoby.count() != 0 and user.email != '':
|
||||||
|
osoba = osoby.first()
|
||||||
|
if osoba.user is None:
|
||||||
|
osoba.user = user
|
||||||
|
logger.info(f'Našel jsem Osobu {osoby.first().email} podle e-mailu')
|
||||||
|
raise EndException
|
||||||
|
|
||||||
# Přesun informací z usera do osoby
|
# Fallback
|
||||||
# pro řešitele již v minule migraci
|
logger.warning(f'Org neměl řešitele, zakládám novou Osobu.')
|
||||||
osoba.jmeno = user.first_name
|
osoba = Osoba(user=user)
|
||||||
osoba.prijmeni = user.last_name
|
|
||||||
osoba.email = user.email
|
# Přesun informací z usera do osoby
|
||||||
user.jmeno = "Použij osobu!"
|
# pro osoby z řešitelů (jediné dosud existující osoby) již v minule migraci
|
||||||
user.prijmeni = "Použij osobu!"
|
osoba.jmeno = user.first_name
|
||||||
user.email = "Použij osobu!"
|
osoba.prijmeni = user.last_name
|
||||||
user.save()
|
osoba.email = user.email
|
||||||
|
user.jmeno += " (Uživatel!)"
|
||||||
|
user.prijmeni += " (Uživatel!)"
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
except EndException: pass
|
||||||
|
|
||||||
# Přesun informací z organizátora do jeho osoby
|
# Přesun informací z organizátora do jeho osoby
|
||||||
osoba.prezdivka = org.prezdivka if org.prezdivka is not None else ''
|
osoba.prezdivka = org.prezdivka if org.prezdivka is not None else ''
|
||||||
|
@ -46,7 +77,10 @@ def fix_problem(apps, schema_editor):
|
||||||
else:
|
else:
|
||||||
pr.autor = None
|
pr.autor = None
|
||||||
if pr.opravovatel is not None:
|
if pr.opravovatel is not None:
|
||||||
pr.opravovatele.add(Organizator.objects.filter(osoba__user=pr.opravovatel).first())
|
if Organizator.objects.filter(osoba__user=pr.opravovatel).first() is not None:
|
||||||
|
pr.opravovatele.add(Organizator.objects.filter(osoba__user=pr.opravovatel).first())
|
||||||
|
else:
|
||||||
|
logger.error(f'WTF, nespárovaný opravovatel {pr.opravovatel} problému {pr}')
|
||||||
pr.save()
|
pr.save()
|
||||||
|
|
||||||
def fix_pohadka(apps, schema_editor):
|
def fix_pohadka(apps, schema_editor):
|
||||||
|
|
|
@ -602,13 +602,18 @@ class Cislo(SeminarModelBase):
|
||||||
png_filename = pathlib.Path(tempfile.mkdtemp(), 'nahled.png')
|
png_filename = pathlib.Path(tempfile.mkdtemp(), 'nahled.png')
|
||||||
|
|
||||||
subprocess.run([
|
subprocess.run([
|
||||||
"convert",
|
"gs",
|
||||||
"-density", "300x300",
|
"-sstdout=%stderr",
|
||||||
"-geometry", "{}x{}".format(VYSKA, sirka),
|
"-dSAFER",
|
||||||
"-background", "white",
|
"-dNOPAUSE",
|
||||||
"-flatten",
|
"-dBATCH",
|
||||||
"{}[0]".format(self.pdf.path), # titulní strana
|
"-dNOPROMPT",
|
||||||
png_filename
|
"-sDEVICE=pngalpha",
|
||||||
|
"-r{}x{}".format(VYSKA, sirka),
|
||||||
|
"-dFirstPage=1d",
|
||||||
|
"-dLastPage=1d",
|
||||||
|
"-sOutputFile=" + str(png_filename),
|
||||||
|
"-f%s" % self.pdf.path
|
||||||
],
|
],
|
||||||
check=True,
|
check=True,
|
||||||
capture_output=True
|
capture_output=True
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load deadliny %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
@ -62,7 +63,9 @@ $(document).ready(function(){
|
||||||
<p>Řešitelé: {{ object.resitele.all | join:", " }}</p>
|
<p>Řešitelé: {{ object.resitele.all | join:", " }}</p>
|
||||||
|
|
||||||
{# https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display #}
|
{# https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display #}
|
||||||
<p>Forma: {{ object.get_forma_display }}, doručeno {{ object.cas_doruceni }}</p>
|
<p>Forma: {{ object.get_forma_display }}</p>
|
||||||
|
|
||||||
|
<p>Doručeno {{ object.cas_doruceni }}, deadline: {{object.cas_doruceni | deadline_html }}</p>
|
||||||
|
|
||||||
{# Soubory: #}
|
{# Soubory: #}
|
||||||
<h3>Přílohy:</h3>
|
<h3>Přílohy:</h3>
|
||||||
|
|
38
seminar/templates/seminar/odevzdavatko/resitel_prehled.html
Normal file
38
seminar/templates/seminar/odevzdavatko/resitel_prehled.html
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load deadliny %}
|
||||||
|
|
||||||
|
{% block custom_css %}
|
||||||
|
<style type=text/css>
|
||||||
|
.dosla_reseni tr th {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.dosla_reseni tr th, .dosla_reseni tr td {
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 1px 10px 1px 10px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock custom_css %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% for rocnik, hodnoceni in podle_rocniku %}
|
||||||
|
<h1>Ročník {{ rocnik }}</h1>
|
||||||
|
<table class="dosla_reseni">
|
||||||
|
<tr>
|
||||||
|
<th>Doručeno</th>
|
||||||
|
<th>Problém</th>
|
||||||
|
<th>Body</th>
|
||||||
|
<th>Deadline</th>
|
||||||
|
</tr>
|
||||||
|
{% for hodn in hodnoceni %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ hodn.reseni.cas_doruceni }}</td>
|
||||||
|
<td>{{ hodn.problem }}</td>
|
||||||
|
<td>{{ hodn.body|default_if_none:"---" }}</td>
|
||||||
|
<td>{{ hodn.reseni.cas_doruceni | deadline_html }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
|
@ -1,11 +1,15 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% load deadliny %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
{% for dl, mnozina_reseni in reseni_podle_deadlinu.items %}
|
||||||
|
<h1>{{ dl.2 | deadline_html }}</h1>
|
||||||
<ul>
|
<ul>
|
||||||
{% for obj in object_list %}
|
{% for obj in mnozina_reseni %}
|
||||||
<li><a href="{% url 'odevzdavatko_detail_reseni' pk=obj.id %}">{{ obj }}</a> ({{ obj.get_forma_display }} {{ obj.cas_doruceni }})
|
<li><a href="{% url 'odevzdavatko_detail_reseni' pk=obj.id %}">{{ obj }}</a> ({{ obj.get_forma_display }} {{ obj.cas_doruceni }})
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -61,6 +61,7 @@
|
||||||
{% include "seminar/profil/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%}
|
{% include "seminar/profil/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
{% if not po_maturite %} {# Vysloužilým účastníkům skrýt editaci školy apod. #}
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h4>
|
<h4>
|
||||||
|
@ -95,6 +96,7 @@
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<input type="submit" value="Změnit">
|
<input type="submit" value="Změnit">
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
<a href="{% url 'logout' %}">Odhlásit se</a><br>
|
<a href="{% url 'logout' %}">Odhlásit se</a><br>
|
||||||
<a href="{% url 'seminar_resitel_edit' %}">Upravit údaje</a><br>
|
<a href="{% url 'seminar_resitel_edit' %}">Upravit údaje</a><br>
|
||||||
|
<a href="{% url 'seminar_resitel_odevzdana_reseni' %}">Odevzdaná řešení</a><br>
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
35
seminar/templatetags/deadliny.py
Normal file
35
seminar/templatetags/deadliny.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
from django import template
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from seminar.utils import TypDeadline, deadline
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.filter(name='deadline')
|
||||||
|
def deadline_text(datum):
|
||||||
|
typ, cislo, dl = deadline(datum)
|
||||||
|
strings = {
|
||||||
|
TypDeadline.PredDeadline: f"1. deadline čísla {cislo} ({dl})",
|
||||||
|
TypDeadline.SousDeadline: f"Soustřeďkový deadline čísla {cislo} ({dl})",
|
||||||
|
TypDeadline.FinalDeadline: f"Finální deadline čísla {cislo} ({dl})",
|
||||||
|
}
|
||||||
|
return strings[typ]
|
||||||
|
|
||||||
|
@register.filter(name='deadline_kratseji')
|
||||||
|
def deadline_kratsi_text(datum):
|
||||||
|
typ, cislo, dl = deadline(datum)
|
||||||
|
strings = {
|
||||||
|
TypDeadline.PredDeadline: f"1. deadline {cislo}",
|
||||||
|
TypDeadline.SousDeadline: f"Soustřeďkový deadline {cislo}",
|
||||||
|
TypDeadline.FinalDeadline: f"Finální deadline {cislo}",
|
||||||
|
}
|
||||||
|
return strings[typ]
|
||||||
|
|
||||||
|
@register.filter(name='deadline_html')
|
||||||
|
def deadline_html(datum):
|
||||||
|
typ, _, _ = deadline(datum)
|
||||||
|
text = deadline_kratsi_text(datum)
|
||||||
|
classes = {
|
||||||
|
TypDeadline.PredDeadline: 'preddeadline',
|
||||||
|
TypDeadline.SousDeadline: 'sous_deadline',
|
||||||
|
TypDeadline.FinalDeadline: 'final_deadline',
|
||||||
|
}
|
||||||
|
return mark_safe(f'<span class="{classes[typ]}">{text}</span>')
|
|
@ -135,6 +135,7 @@ urlpatterns = [
|
||||||
path('prihlasit/', views.LoginView.as_view(), name='login'),
|
path('prihlasit/', views.LoginView.as_view(), name='login'),
|
||||||
path('odhlasit/', views.LogoutView.as_view(), name='logout'),
|
path('odhlasit/', views.LogoutView.as_view(), name='logout'),
|
||||||
path('resitel/', resitel_required(views.ResitelView.as_view()), name='seminar_resitel'),
|
path('resitel/', resitel_required(views.ResitelView.as_view()), name='seminar_resitel'),
|
||||||
|
path('resitel/odevzdana_reseni/', resitel_required(views.PrehledOdevzdanychReseni.as_view()), name='seminar_resitel_odevzdana_reseni'),
|
||||||
path('reset-hesla/', views.PasswordResetView.as_view(), name='reset_password'),
|
path('reset-hesla/', views.PasswordResetView.as_view(), name='reset_password'),
|
||||||
path('zmena-hesla/', views.PasswordChangeView.as_view(), name='change_password'),
|
path('zmena-hesla/', views.PasswordChangeView.as_view(), name='change_password'),
|
||||||
path('reset-hesla/2/', views.PasswordResetDoneView.as_view(), name='reset_password_done'),
|
path('reset-hesla/2/', views.PasswordResetDoneView.as_view(), name='reset_password_done'),
|
||||||
|
|
|
@ -309,7 +309,7 @@ def deadline_v_rocniku(datum, rocnik):
|
||||||
deadliny.append((TypDeadline.FinalDeadline, c, c.datum_deadline))
|
deadliny.append((TypDeadline.FinalDeadline, c, c.datum_deadline))
|
||||||
deadliny = sorted(deadliny, key=lambda x: x[2]) # podle data
|
deadliny = sorted(deadliny, key=lambda x: x[2]) # podle data
|
||||||
for dl in deadliny:
|
for dl in deadliny:
|
||||||
if datum <= dl:
|
if datum <= dl[2]:
|
||||||
# První takový deadline je ten nejtěsnější
|
# První takový deadline je ten nejtěsnější
|
||||||
return dl
|
return dl
|
||||||
|
|
||||||
|
@ -319,20 +319,22 @@ def deadline(datum):
|
||||||
Vrací trojici (TypDeadline, Cislo, datumDeadline: date).
|
Vrací trojici (TypDeadline, Cislo, datumDeadline: date).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if isinstance(datum, datetime.datetime):
|
||||||
|
datum = datum.date()
|
||||||
rok = datum.year
|
rok = datum.year
|
||||||
# Dva ročníky podezřelé z obsahování dat
|
# Dva ročníky podezřelé z obsahování dat
|
||||||
pozdejsi_rocnik = m.Rocnik.filter(prvni_rok=rok)
|
pozdejsi_rocnik = m.Rocnik.objects.filter(prvni_rok=rok)
|
||||||
drivejsi_rocnik = m.Rocnik.filter(druhy_rok=rok)
|
drivejsi_rocnik = m.Rocnik.objects.filter(prvni_rok=rok-1)
|
||||||
if any(
|
if any([
|
||||||
pozdejsi_rocnik.count() > 1,
|
pozdejsi_rocnik.count() > 1,
|
||||||
drivejsi_rocnik.count() > 1,
|
drivejsi_rocnik.count() > 1,
|
||||||
):
|
]):
|
||||||
raise ValueError(f"Více ročníků začíná/končí stejným rokem: {rok}")
|
raise ValueError(f"Více ročníků začíná/končí stejným rokem: {rok}")
|
||||||
pozdejsi_rocnik = pozdejsi_rocnik.first() if pozdejsi_rocnik.count() > 0 else None
|
pozdejsi_rocnik = pozdejsi_rocnik.first() if pozdejsi_rocnik.count() > 0 else None
|
||||||
drivejsi_rocnik = drivejsi_rocnik.first() if drivejsi_rocnik.count() > 0 else None
|
drivejsi_rocnik = drivejsi_rocnik.first() if drivejsi_rocnik.count() > 0 else None
|
||||||
|
|
||||||
# Předpokládáme, že neexistuje číslo, které má deadline ale nemá finální deadline.
|
# Předpokládáme, že neexistuje číslo, které má deadline ale nemá finální deadline.
|
||||||
posledni_deadline_drivejsiho_rocniku = m.Cislo.objects.get(rocnik=drivejsi_rocnik, datum_deadline__isnull=False).datum_deadline
|
posledni_deadline_drivejsiho_rocniku = m.Cislo.objects.filter(rocnik=drivejsi_rocnik, datum_deadline__isnull=False).last().datum_deadline
|
||||||
|
|
||||||
if datum <= posledni_deadline_drivejsiho_rocniku:
|
if datum <= posledni_deadline_drivejsiho_rocniku:
|
||||||
return deadline_v_rocniku(datum, drivejsi_rocnik)
|
return deadline_v_rocniku(datum, drivejsi_rocnik)
|
||||||
|
|
|
@ -8,12 +8,13 @@ from django.db import transaction
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import datetime
|
import datetime
|
||||||
|
from itertools import groupby
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import seminar.models as m
|
import seminar.models as m
|
||||||
import seminar.forms as f
|
import seminar.forms as f
|
||||||
from seminar.forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm
|
from seminar.forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm
|
||||||
from seminar.utils import aktivniResitele, resi_v_rocniku
|
from seminar.utils import aktivniResitele, resi_v_rocniku, deadline
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -24,6 +25,8 @@ logger = logging.getLogger(__name__)
|
||||||
# - ReseniProblemuView
|
# - ReseniProblemuView
|
||||||
# - Detail konkrétního řešení -- všechny soubory, datum, ...
|
# - Detail konkrétního řešení -- všechny soubory, datum, ...
|
||||||
# - DetailReseniView
|
# - DetailReseniView
|
||||||
|
# - Pro řešitele: přehled jejich odevzdaných řešení
|
||||||
|
# - PrehledOdevzdanychReseni
|
||||||
#
|
#
|
||||||
# Taky se může hodit:
|
# Taky se může hodit:
|
||||||
# - Tabulka všech řešitelů x všech problémů?
|
# - Tabulka všech řešitelů x všech problémů?
|
||||||
|
@ -169,7 +172,14 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
|
||||||
return redirect(reverse("odevzdavatko_detail_reseni", kwargs={"pk": jedine_reseni.id}))
|
return redirect(reverse("odevzdavatko_detail_reseni", kwargs={"pk": jedine_reseni.id}))
|
||||||
context = self.get_context_data()
|
context = self.get_context_data()
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
# Kontext automaticky?
|
|
||||||
|
def get_context_data(self, *args, **kwargs):
|
||||||
|
ctx = super().get_context_data(*args, **kwargs)
|
||||||
|
# XXX: Předat groupby do template nejde: https://stackoverflow.com/questions/6906593/itertools-groupby-in-a-django-template
|
||||||
|
# Django má {% regroup %}, ale ten potřebuje, aby klíč byl atribut položky: https://docs.djangoproject.com/en/3.2/ref/templates/builtins/#regroup
|
||||||
|
# Takže rozbalíme groupby do slovníku klíč → seznam sami (dictionary comphrehension)
|
||||||
|
ctx['reseni_podle_deadlinu'] = {k: list(v) for k,v in groupby(ctx['object_list'], lambda r: deadline(r.cas_doruceni))}
|
||||||
|
return ctx
|
||||||
|
|
||||||
## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex
|
## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex
|
||||||
class DetailReseniView(DetailView):
|
class DetailReseniView(DetailView):
|
||||||
|
@ -230,6 +240,29 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
|
||||||
return redirect(success_url)
|
return redirect(success_url)
|
||||||
|
|
||||||
|
|
||||||
|
class PrehledOdevzdanychReseni(ListView):
|
||||||
|
model = m.Hodnoceni
|
||||||
|
template_name = 'seminar/odevzdavatko/resitel_prehled.html'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
if not self.request.user.is_authenticated:
|
||||||
|
raise RuntimeError("Uživatel měl být přihlášený!")
|
||||||
|
resitel = m.Resitel.objects.get(osoba__user=self.request.user)
|
||||||
|
qs = super().get_queryset()
|
||||||
|
qs = qs.filter(reseni__resitele__in=[resitel])
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs):
|
||||||
|
ctx = super().get_context_data(*args, **kwargs)
|
||||||
|
# Ročník určujeme podle čísla, do jehož deadlinu došlo řešení.
|
||||||
|
# Chceme to mít seřazené, takže místo comphrerehsion ručně postavíme pole polí. Django templates neumí použít OrderedDict :-/
|
||||||
|
podle_rocniku = []
|
||||||
|
for rocnik, hodnoceni in groupby(ctx['object_list'], lambda ho: deadline(ho.reseni.cas_doruceni)[1].rocnik):
|
||||||
|
podle_rocniku.append((rocnik, list(hodnoceni)))
|
||||||
|
ctx['podle_rocniku'] = reversed(podle_rocniku) # Od nejnovějšího ročníku
|
||||||
|
# TODO: Umožnit stažení / zobrazení řešení
|
||||||
|
return ctx
|
||||||
|
|
||||||
# Přehled všech řešení kvůli debugování
|
# Přehled všech řešení kvůli debugování
|
||||||
|
|
||||||
class SeznamReseniView(ListView):
|
class SeznamReseniView(ListView):
|
||||||
|
|
|
@ -26,7 +26,7 @@ import seminar.models as m
|
||||||
from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci
|
from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci
|
||||||
#from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva
|
#from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva
|
||||||
from seminar import utils, treelib
|
from seminar import utils, treelib
|
||||||
from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm
|
from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm, ProfileEditFormPoMaturite
|
||||||
import seminar.forms as f
|
import seminar.forms as f
|
||||||
import seminar.templatetags.treenodes as tnltt
|
import seminar.templatetags.treenodes as tnltt
|
||||||
import seminar.views.views_rest as vr
|
import seminar.views.views_rest as vr
|
||||||
|
@ -1095,15 +1095,23 @@ def resitelEditView(request):
|
||||||
user_edit = osoba_edit.user
|
user_edit = osoba_edit.user
|
||||||
## Vytvoření slovníku, kterým předvyplním formulář
|
## Vytvoření slovníku, kterým předvyplním formulář
|
||||||
prefill_1=model_to_dict(user_edit)
|
prefill_1=model_to_dict(user_edit)
|
||||||
if resitel_edit:
|
if resitel_edit and resitel_edit.rok_maturity >= date.today().year:
|
||||||
prefill_2=model_to_dict(resitel_edit)
|
prefill_2=model_to_dict(resitel_edit)
|
||||||
prefill_1.update(prefill_2)
|
prefill_1.update(prefill_2)
|
||||||
prefill_3=model_to_dict(osoba_edit)
|
prefill_3=model_to_dict(osoba_edit)
|
||||||
prefill_1.update(prefill_3)
|
prefill_1.update(prefill_3)
|
||||||
form = ProfileEditForm(initial=prefill_1)
|
if 'datum_narozeni' in prefill_1:
|
||||||
|
prefill_1['datum_narozeni'] = str(prefill_1['datum_narozeni'])
|
||||||
|
if resitel_edit and resitel_edit.rok_maturity < date.today().year:
|
||||||
|
form = ProfileEditFormPoMaturite(initial=prefill_1)
|
||||||
|
else:
|
||||||
|
form = ProfileEditForm(initial=prefill_1)
|
||||||
## Změna údajů a jejich uložení
|
## Změna údajů a jejich uložení
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = ProfileEditForm(request.POST)
|
if resitel_edit and resitel_edit.rok_maturity < date.today().year:
|
||||||
|
form = ProfileEditFormPoMaturite(request.POST)
|
||||||
|
else:
|
||||||
|
form = ProfileEditForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
## Změny v osobě
|
## Změny v osobě
|
||||||
fcd = form.cleaned_data
|
fcd = form.cleaned_data
|
||||||
|
@ -1115,6 +1123,7 @@ def resitelEditView(request):
|
||||||
osoba_edit.ulice = fcd['ulice']
|
osoba_edit.ulice = fcd['ulice']
|
||||||
osoba_edit.mesto = fcd['mesto']
|
osoba_edit.mesto = fcd['mesto']
|
||||||
osoba_edit.psc = fcd['psc']
|
osoba_edit.psc = fcd['psc']
|
||||||
|
osoba_edit.datum_narozeni = fcd['datum_narozeni']
|
||||||
## Změny v osobě s podmínkami
|
## Změny v osobě s podmínkami
|
||||||
if fcd.get('spam',False):
|
if fcd.get('spam',False):
|
||||||
osoba_edit.datum_souhlasu_zasilani = date.today()
|
osoba_edit.datum_souhlasu_zasilani = date.today()
|
||||||
|
@ -1124,7 +1133,7 @@ def resitelEditView(request):
|
||||||
## Neznámá země
|
## Neznámá země
|
||||||
msg = "Unknown country {}".format(fcd['stat_text'])
|
msg = "Unknown country {}".format(fcd['stat_text'])
|
||||||
|
|
||||||
if resitel_edit:
|
if resitel_edit and resitel_edit.rok_maturity >= date.today().year:
|
||||||
## Změny v řešiteli
|
## Změny v řešiteli
|
||||||
resitel_edit.skola = fcd['skola']
|
resitel_edit.skola = fcd['skola']
|
||||||
resitel_edit.rok_maturity = fcd['rok_maturity']
|
resitel_edit.rok_maturity = fcd['rok_maturity']
|
||||||
|
@ -1140,7 +1149,7 @@ def resitelEditView(request):
|
||||||
return formularOKView(request)
|
return formularOKView(request)
|
||||||
else:
|
else:
|
||||||
## Stránka před odeslaním formuláře = předvyplněný formulář
|
## Stránka před odeslaním formuláře = předvyplněný formulář
|
||||||
return render(request, 'seminar/profil/edit.html', {'form': form})
|
return render(request, 'seminar/profil/edit.html', {'form': form, 'po_maturite': resitel_edit and resitel_edit.rok_maturity < date.today().year})
|
||||||
|
|
||||||
def prihlaskaView(request):
|
def prihlaskaView(request):
|
||||||
generic_logger = logging.getLogger('seminar.prihlaska')
|
generic_logger = logging.getLogger('seminar.prihlaska')
|
||||||
|
|
0
various/__init__.py
Normal file
0
various/__init__.py
Normal file
3
various/admin.py
Normal file
3
various/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
5
various/apps.py
Normal file
5
various/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class VariousConfig(AppConfig):
|
||||||
|
name = 'various'
|
14
various/context_processors.py
Normal file
14
various/context_processors.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
def april(req):
|
||||||
|
if 'X-April' in req.headers:
|
||||||
|
try:
|
||||||
|
year = int(req.headers['X-April'])
|
||||||
|
return {'april': year}
|
||||||
|
except:
|
||||||
|
pass # Fall-back to regular behaviour
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
today = datetime.date.today()
|
||||||
|
if today.day == 1 and today.month == 4:
|
||||||
|
return {'april': today.year}
|
||||||
|
return {}
|
||||||
|
|
0
various/migrations/__init__.py
Normal file
0
various/migrations/__init__.py
Normal file
3
various/models.py
Normal file
3
various/models.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
3
various/tests.py
Normal file
3
various/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
3
various/views.py
Normal file
3
various/views.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
Loading…
Reference in a new issue