Browse Source

Move personalni do aplikace personalni

export_seznamu_prednasek
Jonas Havelka 3 years ago
parent
commit
0cd1c3ef1a
  1. 1
      mamweb/settings_common.py
  2. 4
      mamweb/urls.py
  3. 4
      personalni/__init__.py
  4. 48
      personalni/admin.py
  5. 5
      personalni/apps.py
  6. 221
      personalni/forms.py
  7. 0
      personalni/migrations/__init__.py
  8. 0
      personalni/static/personalni/prihlaska.js
  9. 0
      personalni/templates/personalni/profil/orgorozcestnik.html
  10. 0
      personalni/templates/personalni/profil/resitel.html
  11. 105
      personalni/templates/personalni/udaje/edit.html
  12. 0
      personalni/templates/personalni/udaje/gdpr.html
  13. 123
      personalni/templates/personalni/udaje/prihlaska.html
  14. 0
      personalni/templates/personalni/udaje/prihlaska_field.html
  15. 24
      personalni/urls.py
  16. 306
      personalni/views.py
  17. 44
      seminar/admin.py
  18. 213
      seminar/forms.py
  19. 2
      seminar/models/__init__.py
  20. 22
      seminar/models/base.py
  21. 476
      seminar/models/models_all.py
  22. 6
      seminar/models/odevzdavatko.py
  23. 438
      seminar/models/personalni.py
  24. 105
      seminar/templates/seminar/profil/edit.html
  25. 123
      seminar/templates/seminar/profil/prihlaska.html
  26. 18
      seminar/urls.py
  27. 288
      seminar/views/views_all.py

1
mamweb/settings_common.py

@ -140,6 +140,7 @@ INSTALLED_APPS = (
'aesop', 'aesop',
'odevzdavatko', 'odevzdavatko',
'vysledkovky', 'vysledkovky',
'personalni',
# Admin upravy: # Admin upravy:

4
mamweb/urls.py

@ -26,6 +26,10 @@ urlpatterns = [
# Prednaskova aplikace (ma vlastni podadresare) # Prednaskova aplikace (ma vlastni podadresare)
path('', include('prednasky.urls')), path('', include('prednasky.urls')),
# Personalni aplikace (ma vlastni podadresare)
# (profil, osobní údaje, ..., ne autentizace, viz dále)
path('', include('personalni.urls')),
# Autentizační aplikace (ma vlastni podadresare) # Autentizační aplikace (ma vlastni podadresare)
path('', include('various.autentizace.urls')), path('', include('various.autentizace.urls')),

4
personalni/__init__.py

@ -0,0 +1,4 @@
"""
Obsahuje vše okolo registrace a osobních údajů (ne přihlášení a změnu hesla).
Také obsahuje rozcestníky a Řešitele s Organizátorem.
"""

48
personalni/admin.py

@ -0,0 +1,48 @@
from django.contrib import admin
from django.contrib.auth.models import Group
from django_reverse_admin import ReverseModelAdmin
import seminar.models as m
@admin.register(m.Osoba)
class OsobaAdmin(admin.ModelAdmin):
actions = ['synchronizuj_maily', 'udelej_orgem']
def synchronizuj_maily(self, request, queryset):
for o in queryset:
if o.user is not None:
u = o.user
u.email = o.email
u.save()
self.message_user(request, "E-maily synchronizovány.")
synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů"
def udelej_orgem(self,request,queryset):
org_group = Group.objects.get(name='org')
print(queryset)
for o in queryset:
user = o.user
print(user)
user.groups.add(org_group)
user.is_staff = True
user.save()
org = m.Organizator.objects.create(osoba=o)
org.save()
udelej_orgem.short_description = "Udělej vybraných osob organizátory"
@admin.register(m.Organizator)
class OrganizatorAdmin(admin.ModelAdmin):
search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka']
class OsobaInline(admin.TabularInline):
model = m.Osoba
@admin.register(m.Resitel)
class ResitelAdmin(ReverseModelAdmin):
search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka']
ordering = ('osoba__jmeno','osoba__prijmeni')
inline_type = 'stacked'
inline_reverse = ['osoba']
admin.site.register(m.Skola)
admin.site.register(m.Prijemce)

5
personalni/apps.py

@ -0,0 +1,5 @@
from django.apps import AppConfig
class PersonalniConfig(AppConfig):
name = 'personalni'

221
personalni/forms.py

@ -0,0 +1,221 @@
from django import forms
from dal import autocomplete
from django.contrib.auth.forms import PasswordResetForm
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User
from seminar.models import Skola, Resitel, Osoba
from datetime import date
import logging
# pro přidání políčka do formuláře je potřeba
# - mít v modelu tu položku, kterou chci upravovat
# - přidat do views (prihlaskaView, resitelEditView)
# - přidat do forms
# - includovat do html
class DateInput(forms.DateInput):
# aby se datum dalo vybírat z kalendáře
input_type = 'date'
class TelInput(forms.TextInput):
# tohle je možná k niřemu, ale alepsoň to mění input type a nic to nekazí
input_type = 'tel'
input_pattern="^[+]?[()/0-9. -]{9,}$"
class PrihlaskaForm(PasswordResetForm):
username = forms.CharField(label='Přihlašovací jméno',
max_length=256,
required=True,
help_text='Tímto jménem se následně budeš přihlašovat pro odevzdání řešení a další činnosti v semináři')
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 a číslo popisné', 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)
skola = forms.ModelChoiceField(label="Škola",
queryset=Skola.objects.all(),
widget=autocomplete.ModelSelect2(
url='autocomplete_skola',
attrs = {'data-placeholder--id': '-1',
'data-placeholder--text' : '---',
'data-allow-clear': 'true'})
,required=False)
skola_nazev = forms.CharField(label='Název školy', max_length=256, required=False)
skola_adresa = forms.CharField(label='Adresa školy', max_length=256, required=False)
# trida = forms.CharField(label='Třída',max_length=10, required=True)
rok_maturity = forms.IntegerField(
label='Rok maturity',
min_value=date.today().year,
max_value=date.today().year+8,
required=True)
zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True)
zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat e-mailem upozornění na vydání nového čísla', required=False)
gdpr = forms.BooleanField(label='Souhlasím se zpracováním osobních údajů', required=True)
spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False)
def clean_username(self):
err_logger = logging.getLogger('seminar.prihlaska.problem')
username = self.cleaned_data.get('username')
try:
User.objects.get(username=username)
msg = "Username {} exists".format(username)
err_logger.info(msg)
raise forms.ValidationError('Přihlašovací jméno je již použito')
except ObjectDoesNotExist:
pass
return username
def clean_email(self):
err_logger = logging.getLogger('seminar.prihlaska.problem')
email = self.cleaned_data.get('email')
try:
osoba = Osoba.objects.get(email=email)
msg = "Email {} exists".format(email)
if osoba.user is not None:
err_logger.info(msg)
raise forms.ValidationError('E-mail je již použit')
else:
msg += ', but currently has no User, so allowing registration.'
err_logger.info(msg)
except ObjectDoesNotExist:
pass
return email
def clean(self):
super().clean()
err_logger = logging.getLogger('seminar.prihlaska.problem')
data = self.cleaned_data
if data.get('stat') != 'other' and data.get('stat_text') != '':
self.add_error('stat',forms.ValidationError('Nelze mít vybraný stát z menu a zároven zapsaný textem'))
if data.get('skola') and (data.get('skola_nazev') or data.get('skola_adresa')):
self.add_error('skola',forms.ValidationError('Pokud je škola v seznamu, nevypisujte ji ručně, pokud není, zrušte výběr ze seznamu (křížek vpravo)'))
if not data.get('skola'):
if data.get('skola_nazev')=='' and data.get('skola_adresa')=='':
self.add_error('skola',forms.ValidationError('Je nutné vyplnit školu'))
elif data.get('skola_nazev')=='':
self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy'))
elif data.get('skola_adresa')=='':
self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy'))
class ProfileEditForm(forms.Form):
username = forms.CharField(label='Přihlašovací jméno',
max_length=256,
required=False,
disabled=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)
skola = forms.ModelChoiceField(label="Škola",
queryset=Skola.objects.all(),
widget=autocomplete.ModelSelect2(
url='autocomplete_skola',
attrs = {'data-placeholder--id': '-1',
'data-placeholder--text' : '---',
'data-allow-clear': 'true'})
,required=False)
skola_nazev = forms.CharField(label='Název školy', max_length=256, required=False)
skola_adresa = forms.CharField(label='Adresa školy', max_length=256, required=False)
# trida = forms.CharField(label='Třída',max_length=10, required=True)
rok_maturity = forms.IntegerField(
label='Rok maturity',
min_value=date.today().year,
max_value=date.today().year+8,
required=True)
zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True)
zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat email s upozorněním na vydání nového čísla', required=False)
spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False)
# def clean_username(self):
# err_logger = logging.getLogger('seminar.prihlaska.problem')
# username = self.cleaned_data.get('username')
# try:
# User.objects.get(username=username)
# msg = "Username {} exists".format(username)
# err_logger.info(msg)
# raise forms.ValidationError('Přihlašovací jméno je již použito')
#
# except ObjectDoesNotExist:
# pass
# return username
#
def clean_email(self):
err_logger = logging.getLogger('seminar.prihlaska.problem')
email = self.cleaned_data.get('email')
try:
Osoba.objects.exclude(user__username=self.username).get(email=email)
msg = "Email {} exists (in edit)".format(email)
err_logger.info(msg)
raise forms.ValidationError('Email je již použit')
except ObjectDoesNotExist:
pass
return email
#def clean(self):
# super().clean()
#
# err_logger = logging.getLogger('seminar.prihlaska.problem')
# data = self.cleaned_data
# if data.get('password') != data.get('password_check'):
# self.add_error('password_check',forms.ValidationError('Hesla se neshodují'))
# if data.get('stat') != '' and data.get('stat_text') != '':
# self.add_error('stat',forms.ValidationError('Nelze mít vybraný stát z menu a zároven zapsaný textem'))
# if data.get('skola') and (data.get('skola_nazev') or data.get('skola_adresa')):
# self.add_error('skola',forms.ValidationError('Pokud je škola v seznamu, nevypisujte ji ručně, pokud není, zrušte výběr ze seznamu (křížek vpravo)'))
# if not data.get('skola'):
# if data.get('skola_nazev')=='' and data.get('skola_adresa')=='':
# self.add_error('skola',forms.ValidationError('Je nutné vyplnit školu'))
# elif data.get('skola_nazev')=='':
# self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy'))
# elif data.get('skola_adresa')=='':
# self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy'))
class PoMaturiteProfileEditForm(ProfileEditForm):
rok_maturity = forms.IntegerField(
label='Rok maturity',
required=True)

0
personalni/migrations/__init__.py

0
seminar/static/seminar/prihlaska.js → personalni/static/personalni/prihlaska.js

0
seminar/templates/seminar/orgorozcestnik.html → personalni/templates/personalni/profil/orgorozcestnik.html

0
seminar/templates/seminar/profil/resitel.html → personalni/templates/personalni/profil/resitel.html

105
personalni/templates/personalni/udaje/edit.html

@ -0,0 +1,105 @@
{% extends "base.html" %}
{% load staticfiles %}
{% block script %}
<script src="{% static 'personalni/prihlaska.js' %}"></script>
{% endblock %}
<!--
# pro přidání políčka do formuláře je potřeba
# - mít v modelu tu položku, kterou chci upravovat
# - přidat do views (prihlaskaView, resitelEditView)
# - přidat do forms
# - includovat do html
-->
{% block content %}
<h1>
{% block nadpis1a %}{% block nadpis1b %}
Změna osobních údajů
{% endblock %}{% endblock %}
</h1>
<form action="{% url 'seminar_resitel_edit' %}" method="post">
{% csrf_token %}
{{form.non_field_errors}}
<hr>
<h4>
Přihlašovací údaje
</h4>
<table class="form">
{% include "personalni/udaje/prihlaska_field.html" with field=form.username %}
</table>
<hr>
<h4>
Osobní údaje
</h4>
<table class="form">
{% include "personalni/udaje/prihlaska_field.html" with field=form.jmeno %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.prijmeni %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.pohlavi_muz%}
{% include "personalni/udaje/prihlaska_field.html" with field=form.email %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.telefon %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.datum_narozeni %}
</table>
<hr>
<h4>
Bydliště
</h4>
<table class="form">
{% include "personalni/udaje/prihlaska_field.html" with field=form.ulice %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.mesto %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.psc %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.stat %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%}
</table>
<hr>
<h4>
Škola
</h4>
<table class="form">
{% include "personalni/udaje/prihlaska_field.html" with field=form.skola %}
<tr><td colspan="2" ><button id="id_skola_text_button" type="button">Škola není v seznamu</button></td></tr>
<tr><td id="id_li_skola_vypln" colspan="2">Vyplň prosím celý název a adresu školy.</td></tr>
{% include "personalni/udaje/prihlaska_field.html" with field=form.skola_nazev id="id_li_skola_nazev" %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.skola_adresa id="id_li_skola_adresa" %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.rok_maturity %}
</table>
<hr>
<h4>
Pošta
</h4>
<table class="form">
{% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_emailem %}
</table>
<hr>
<h4>
Zasílání propagačních materiálů
</h4>
<table class="form">
{% include "personalni/udaje/prihlaska_field.html" with field=form.spam %}
</table>
<hr>
<input type="submit" value="Změnit">
</form>
<script>
$("#id_stat").on("change",addrCountryChanged);
$("#id_skola_text_button").on("click",schoolNotInList);
</script>
{% endblock %}

0
seminar/templates/seminar/profil/gdpr.html → personalni/templates/personalni/udaje/gdpr.html

123
personalni/templates/personalni/udaje/prihlaska.html

@ -0,0 +1,123 @@
{% extends "base.html" %}
{% load staticfiles %}
{% block script %}
<script src="{% static 'personalni/prihlaska.js' %}"></script>
{% endblock %}
<!--
# pro přidání políčka do formuláře je potřeba
# - mít v modelu tu položku, kterou chci upravovat
# - přidat do views (prihlaskaView, resitelEditView)
# - přidat do forms
# - includovat do html
-->
{% block content %}
<h1>
{% block nadpis1a %}{% block nadpis1b %}
Přihláška do semináře
{% endblock %}{% endblock %}
</h1>
<p><b>Tučně</b> popsaná pole jsou povinná.</p>
<form action="{% url 'seminar_prihlaska' %}" method="post">
{% csrf_token %}
{{form.non_field_errors}}
<hr>
<h4>
Přihlašovací údaje
</h4>
<table class="form">
{% include "personalni/udaje/prihlaska_field.html" with field=form.username %}
{# {% include "personalni/udaje/prihlaska_field.html" with field=form.password %}#}
{# {% include "personalni/udaje/prihlaska_field.html" with field=form.password_check %}#}
</table>
<hr>
<h4>
Osobní údaje
</h4>
<table class="form">
{% include "personalni/udaje/prihlaska_field.html" with field=form.jmeno %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.prijmeni %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.pohlavi_muz%}
{% include "personalni/udaje/prihlaska_field.html" with field=form.email %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.telefon %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.datum_narozeni %}
</table>
<hr>
<h4>
Bydliště
</h4>
<table class="form">
{% include "personalni/udaje/prihlaska_field.html" with field=form.ulice %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.mesto %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.psc %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.stat %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%}
</table>
<hr>
<h4>
Škola
</h4>
<table class="form">
{% include "personalni/udaje/prihlaska_field.html" with field=form.skola %}
<tr><td colspan="2" ><button id="id_skola_text_button" type="button">Škola není v seznamu</button></td></tr>
<tr><td id="id_li_skola_vypln" colspan="2">Vyplň prosím celý název a adresu školy.</td></tr>
{% include "personalni/udaje/prihlaska_field.html" with field=form.skola_nazev id="id_li_skola_nazev" %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.skola_adresa id="id_li_skola_adresa" %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.rok_maturity %}
</table>
<hr>
<h4>
Pošta
</h4>
<table class="form">
{% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat %}
{% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_emailem %}
</table>
<hr>
<h4>
GDPR
</h4>
{% include "personalni/udaje/gdpr.html" %}
<table class="form">
{% include "personalni/udaje/prihlaska_field.html" with field=form.gdpr %}
</table>
<hr>
<h4>
Zasílání propagačních materiálů
</h4>
<table class="form">
{% include "personalni/udaje/prihlaska_field.html" with field=form.spam %}
</table>
<hr>
<input type="submit" value="Odeslat">
</form>
<script>
$("#id_stat").on("change",addrCountryChanged);
$("#id_skola_text_button").on("click",schoolNotInList);
</script>
{% endblock %}

0
seminar/templates/seminar/profil/prihlaska_field.html → personalni/templates/personalni/udaje/prihlaska_field.html

24
personalni/urls.py

@ -0,0 +1,24 @@
from django.urls import path
from django.contrib.auth.decorators import login_required
from . import views
from seminar.utils import org_required
urlpatterns = [
path(
'org/rozcestnik/',
org_required(views.OrgoRozcestnikView.as_view()),
name='seminar_org_rozcestnik'
),
path('prihlaska/', views.prihlaskaView, name='seminar_prihlaska'),
path(
'resitel/osobni-udaje/',
login_required(views.resitelEditView),
name='seminar_resitel_edit'
),
# Obecný view na profil -- orgům dá rozcestník, řešitelům jejich stránku
path('profil/', views.profilView, name='profil'),
]

306
personalni/views.py

@ -0,0 +1,306 @@
from django.shortcuts import render
from django.urls import reverse
from django.views import generic
from django.db.models import Q
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic.base import TemplateView
from django.contrib.auth.models import User, Permission, Group
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import transaction
import seminar.models as s
import seminar.models as m
from .forms import PrihlaskaForm, ProfileEditForm, PoMaturiteProfileEditForm
from datetime import date
import logging
from seminar.views import formularOKView
from various.autentizace.views import LoginView
from various.autentizace.utils import posli_reset_hesla
from django.forms.models import model_to_dict
class OrgoRozcestnikView(TemplateView):
""" Zobrazí organizátorský rozcestník."""
template_name = 'personalni/profil/orgorozcestnik.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['posledni_soustredeni'] = s.Soustredeni.objects.order_by('-datum_konce').first()
nastaveni = s.Nastaveni.objects.first()
aktualni_rocnik = nastaveni.aktualni_rocnik
context['posledni_cislo_url'] = nastaveni.aktualni_cislo.verejne_url()
# TODO možná chceme odkazovat na právě rozpracované číslo, a ne to poslední vydané
# pokud nechceme haluzit kód (= poradi) dalšího čísla, bude asi potřeba jít
# přes treenody (a dát si přitom pozor na MezicisloNode)
neobodovana_reseni = s.Hodnoceni.objects.filter(body__isnull=True)
reseni_mimo_cislo = s.Hodnoceni.objects.filter(cislo_body__isnull=True)
context['pocet_neobodovanych_reseni'] = neobodovana_reseni.count()
context['pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.count()
u = self.request.user
os = s.Osoba.objects.get(user=u)
organizator = s.Organizator.objects.get(osoba=os)
context['muj_pocet_neobodovanych_reseni'] = neobodovana_reseni.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).distinct().count()
context['muj_pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).count()
#FIXME: přidat stav='STAV_ZADANY'
temata = s.Tema.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
rocnik=aktualni_rocnik).distinct()
ulohy = s.Uloha.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
cislo_zadani__rocnik=aktualni_rocnik).distinct()
clanky = s.Clanek.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
cislo__rocnik=aktualni_rocnik).distinct()
context['temata'] = temata
context['ulohy'] = ulohy
context['clanky'] = clanky
context['organizator'] = organizator
return context
#content_type = 'text/plain; charset=UTF8'
#XXX
class ResitelView(LoginRequiredMixin,generic.DetailView):
model = s.Resitel
template_name = 'personalni/profil/resitel.html'
def get_object(self, queryset=None):
print(self.request.user)
return s.Resitel.objects.get(osoba__user=self.request.user)
### Formulare
# pro přidání políčka do formuláře je potřeba
# - mít v modelu tu položku, kterou chci upravovat
# - přidat do views (prihlaskaView, resitelEditView)
# - přidat do forms
# - includovat do html
def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data):
msg = "{}, form_hash:{}".format(msg,hash(frozenset(form_data.items)))
logger.warn(msg)
gdpr_logger.warn(msg+", form:{}".format(form_data))
@sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola')
def resitelEditView(request):
err_logger = logging.getLogger('seminar.prihlaska.problem')
## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli
u = request.user
osoba_edit = s.Osoba.objects.get(user=u)
if hasattr(osoba_edit,'resitel'):
resitel_edit = osoba_edit.resitel
else:
resitel_edit = None
user_edit = osoba_edit.user
## Vytvoření slovníku, kterým předvyplním formulář
prefill_1=model_to_dict(user_edit)
if resitel_edit:
prefill_2=model_to_dict(resitel_edit)
prefill_1.update(prefill_2)
prefill_3=model_to_dict(osoba_edit)
prefill_1.update(prefill_3)
if 'datum_narozeni' in prefill_1:
prefill_1['datum_narozeni'] = str(prefill_1['datum_narozeni'])
if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year:
form = PoMaturiteProfileEditForm(initial=prefill_1)
else:
form = ProfileEditForm(initial=prefill_1)
## Změna údajů a jejich uložení
if request.method == 'POST':
POST = request.POST.copy()
POST["username"] = osoba_edit.user.username
if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year:
form = PoMaturiteProfileEditForm(POST)
else:
form = ProfileEditForm(POST)
form.username = user_edit.username
if form.is_valid():
## Změny v osobě
fcd = form.cleaned_data
form_hash = hash(frozenset(fcd.items()))
form_logger = logging.getLogger('seminar.prihlaska.form')
form_logger.info("EDIT:" + str(fcd) + str(form_hash)) # TODO možná logovat jinak
osoba_edit.jmeno = fcd['jmeno']
osoba_edit.prijmeni = fcd['prijmeni']
osoba_edit.pohlavi_muz = fcd['pohlavi_muz']
osoba_edit.email = fcd['email']
osoba_edit.telefon = fcd['telefon']
osoba_edit.ulice = fcd['ulice']
osoba_edit.mesto = fcd['mesto']
osoba_edit.psc = fcd['psc']
osoba_edit.datum_narozeni = fcd['datum_narozeni']
## Změny v osobě s podmínkami
if fcd.get('spam',False):
osoba_edit.datum_souhlasu_zasilani = date.today()
if fcd.get('stat','') in ('CZ','SK'):
osoba_edit.stat = fcd['stat']
else:
## Neznámá země
msg = "Unknown country {}".format(fcd['stat_text'])
if resitel_edit:
## Změny v řešiteli
resitel_edit.skola = fcd['skola']
resitel_edit.rok_maturity = fcd['rok_maturity']
resitel_edit.zasilat = fcd['zasilat']
resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
if fcd.get('skola'):
resitel_edit.skola = fcd['skola']
else:
# Unknown school - log it
msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
resitel_edit.save()
osoba_edit.save()
return formularOKView(request, text=f'Údaje byly úspěšně uloženy. <a href="{reverse("profil")}">Vrátit se zpět na profil.</a>')
return render(request, 'personalni/udaje/edit.html', {'form': form})
@sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola')
def prihlaskaView(request):
generic_logger = logging.getLogger('seminar.prihlaska')
err_logger = logging.getLogger('seminar.prihlaska.problem')
form_logger = logging.getLogger('seminar.prihlaska.form')
if request.method == 'POST':
form = PrihlaskaForm(request.POST)
# TODO vyresit, co se bude v jakych situacich zobrazovat
if form.is_valid():
generic_logger.info("Form valid")
fcd = form.cleaned_data
form_hash = hash(frozenset(fcd.items()))
form_logger.info(str(fcd) + str(form_hash)) # TODO možná logovat jinak
with transaction.atomic():
u = User.objects.create_user(
username=fcd['username'],
email = fcd['email'])
u.save()
resitel_perm = Permission.objects.filter(codename__exact='resitel').first()
u.user_permissions.add(resitel_perm)
resitel_grp = Group.objects.filter(name__exact='resitel').first()
u.groups.add(resitel_grp)
o = s.Osoba(
jmeno = fcd['jmeno'],
prijmeni = fcd['prijmeni'],
pohlavi_muz = fcd['pohlavi_muz'],
email = fcd['email'],
telefon = fcd.get('telefon',''),
datum_narozeni = fcd.get('datum_narozeni',None),
datum_souhlasu_udaje = date.today(),
datum_registrace = date.today(),
ulice = fcd.get('ulice',''),
mesto = fcd.get('mesto',''),
psc = fcd.get('psc',''),
poznamka = str(fcd)
)
if fcd.get('spam',False):
o.datum_souhlasu_zasilani = date.today()
if fcd.get('stat','') in ('CZ','SK'):
o.stat = fcd['stat']
else:
# Unknown country - log it
msg = "Unknown country {}".format(fcd['stat_text'])
err_logger.warn(msg + str(form_hash))
# Dovolujeme doregistraci uživatele pro existující mail, takže naopak chceme doplnit/aktualizovat údaje do stávajícího objektu
try:
orig_osoba = m.Osoba.objects.get(email=fcd['email'])
orig_osoba.poznamka += '\nDOREGISTRACE K EXISTUJÍCÍMU E-MAILU, diff níže.'
except m.Osoba.DoesNotExist:
# Trik: Budeme aktualizovat údaje nové osoby, takže se asi nic nezmění, ale fungovat to bude.
orig_osoba = o
# Porovnání údajů
assert orig_osoba.user is None, "Právě-registrující-se osoba už má Uživatele!"
osoba_attrs = ['jmeno', 'prijmeni', 'pohlavi_muz', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'stat', 'datum_souhlasu_udaje', 'datum_souhlasu_zasilani', 'datum_registrace']
diffattrs = []
for attr in osoba_attrs:
new = getattr(o, attr)
old = getattr(orig_osoba, attr)
if new != old:
orig_osoba.poznamka += f'\nRozdíl v {attr}: Původní {old}, nový {new}'
diffattrs.append(f'Osoba.{attr}')
setattr(orig_osoba, attr, new)
# Datum registrace chceme původní / nižší:
orig_osoba.datum_registrace = min(orig_osoba.datum_registrace, o.datum_registrace)
# Od této chvíle dál je správná osoba ta "původní", novou podle formuláře si ale zachováme
o, o_form = orig_osoba, o
o.save()
o.user = u
o.save()
# Jednoduchá kvazi-kontrola duplicitních Osob
kolize = m.Osoba.objects.filter(jmeno=o.jmeno, prijmeni=o.prijmeni)
if kolize.count() > 1: # Jednu z nich jsme právě uložili
err_logger.warning(f'Zaregistrovala se osoba s kolizním jménem. ID osob: {[o.id for o in kolize]}')
r = s.Resitel(
rok_maturity = fcd['rok_maturity'],
zasilat = fcd['zasilat'],
zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
)
if fcd.get('skola'):
r.skola = fcd['skola']
else:
# Unknown school - log it
msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
err_logger.warn(msg + str(form_hash))
# Porovnání údajů u řešitele
try:
orig_resitel = o.resitel
orig_resitel.poznamka += '\nDOREGISTRACE ŘEŠITELE, diff:'
except m.Resitel.DoesNotExist:
# Stejný trik:
orig_resitel = r
resitel_attrs = ['skola', 'rok_maturity', 'zasilat', 'zasilat_cislo_emailem']
for attr in resitel_attrs:
new = getattr(r, attr)
old = getattr(orig_resitel, attr)
if new != old:
orig_resitel.poznamka += f'\nRozdíl v {attr}: Původní {old}, nový {new}'
diffattrs.append(f'Resitel.{attr}')
setattr(orig_resitel, attr, new)
r, r_form = orig_resitel, r
r.osoba = o # Tohle by mělo být bezpečné…
r.save()
if diffattrs: err_logger.warning(f'Different fields when matching Řešitel id {r.id} or Osoba id {o.id}: {diffattrs}')
posli_reset_hesla(u, request)
return formularOKView(request, text='Na tvůj e-mail jsme právě poslali odkaz pro nastavení hesla.')
# if a GET (or any other method) we'll create a blank form
else:
form = PrihlaskaForm()
return render(request, 'personalni/udaje/prihlaska.html', {'form': form})
# Jen hloupé rozhazovátko
def profilView(request):
user = request.user
if user.has_perm('auth.org'):
return OrgoRozcestnikView.as_view()(request)
if user.has_perm('auth.resitel'):
return ResitelView.as_view()(request)
else:
return LoginView.as_view()(request)

44
seminar/admin.py

@ -1,12 +1,9 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.models import Group
from django.db import models from django.db import models
from django.forms import widgets, ModelForm from django.forms import widgets, ModelForm
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter
from reversion.admin import VersionAdmin
from django_reverse_admin import ReverseModelAdmin
from solo.admin import SingletonModelAdmin from solo.admin import SingletonModelAdmin
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -17,8 +14,6 @@ from seminar.utils import hlavni_problem
import seminar.models as m import seminar.models as m
import seminar.treelib as tl import seminar.treelib as tl
admin.site.register(m.Skola)
admin.site.register(m.Prijemce)
admin.site.register(m.Rocnik) admin.site.register(m.Rocnik)
class CisloForm(ModelForm): class CisloForm(ModelForm):
@ -105,45 +100,6 @@ class CisloAdmin(admin.ModelAdmin):
force_publish.short_description = 'Zveřejnit vybraná čísla a všechny návrhy úloh v nich učinit zadanými' force_publish.short_description = 'Zveřejnit vybraná čísla a všechny návrhy úloh v nich učinit zadanými'
@admin.register(m.Osoba)
class OsobaAdmin(admin.ModelAdmin):
actions = ['synchronizuj_maily', 'udelej_orgem']
def synchronizuj_maily(self, request, queryset):
for o in queryset:
if o.user is not None:
u = o.user
u.email = o.email
u.save()
self.message_user(request, "E-maily synchronizovány.")
synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů"
def udelej_orgem(self,request,queryset):
org_group = Group.objects.get(name='org')
print(queryset)
for o in queryset:
user = o.user
print(user)
user.groups.add(org_group)
user.is_staff = True
user.save()
org = m.Organizator.objects.create(osoba=o)
org.save()
udelej_orgem.short_description = "Udělej vybraných osob organizátory"
@admin.register(m.Organizator)
class OrganizatorAdmin(admin.ModelAdmin):
search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka']
class OsobaInline(admin.TabularInline):
model = m.Osoba
@admin.register(m.Resitel)
class ResitelAdmin(ReverseModelAdmin):
search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka']
ordering = ('osoba__jmeno','osoba__prijmeni')
inline_type = 'stacked'
inline_reverse = ['osoba']
@admin.register(m.Problem) @admin.register(m.Problem)
class ProblemAdmin(PolymorphicParentModelAdmin): class ProblemAdmin(PolymorphicParentModelAdmin):

213
seminar/forms.py

@ -1,225 +1,12 @@
from django import forms from django import forms
from dal import autocomplete
from django.contrib.auth.forms import PasswordResetForm
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User
from .models import Skola, Resitel, Osoba
import seminar.models as m import seminar.models as m
from datetime import date
import logging
# pro přidání políčka do formuláře je potřeba # pro přidání políčka do formuláře je potřeba
# - mít v modelu tu položku, kterou chci upravovat # - mít v modelu tu položku, kterou chci upravovat
# - přidat do views (prihlaskaView, resitelEditView) # - přidat do views (prihlaskaView, resitelEditView)
# - přidat do forms # - přidat do forms
# - includovat do html # - includovat do html
class DateInput(forms.DateInput):
# aby se datum dalo vybírat z kalendáře
input_type = 'date'
class TelInput(forms.TextInput):
# tohle je možná k niřemu, ale alepsoň to mění input type a nic to nekazí
input_type = 'tel'
input_pattern="^[+]?[()/0-9. -]{9,}$"
class PrihlaskaForm(PasswordResetForm):
username = forms.CharField(label='Přihlašovací jméno',
max_length=256,
required=True,
help_text='Tímto jménem se následně budeš přihlašovat pro odevzdání řešení a další činnosti v semináři')
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 a číslo popisné', 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)
skola = forms.ModelChoiceField(label="Škola",
queryset=Skola.objects.all(),
widget=autocomplete.ModelSelect2(
url='autocomplete_skola',
attrs = {'data-placeholder--id': '-1',
'data-placeholder--text' : '---',
'data-allow-clear': 'true'})
,required=False)
skola_nazev = forms.CharField(label='Název školy', max_length=256, required=False)
skola_adresa = forms.CharField(label='Adresa školy', max_length=256, required=False)
# trida = forms.CharField(label='Třída',max_length=10, required=True)
rok_maturity = forms.IntegerField(
label='Rok maturity',
min_value=date.today().year,
max_value=date.today().year+8,
required=True)
zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True)
zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat e-mailem upozornění na vydání nového čísla', required=False)
gdpr = forms.BooleanField(label='Souhlasím se zpracováním osobních údajů', required=True)
spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False)
def clean_username(self):
err_logger = logging.getLogger('seminar.prihlaska.problem')
username = self.cleaned_data.get('username')
try:
User.objects.get(username=username)
msg = "Username {} exists".format(username)
err_logger.info(msg)
raise forms.ValidationError('Přihlašovací jméno je již použito')
except ObjectDoesNotExist:
pass
return username
def clean_email(self):
err_logger = logging.getLogger('seminar.prihlaska.problem')
email = self.cleaned_data.get('email')
try:
osoba = Osoba.objects.get(email=email)
msg = "Email {} exists".format(email)
if osoba.user is not None:
err_logger.info(msg)
raise forms.ValidationError('E-mail je již použit')
else:
msg += ', but currently has no User, so allowing registration.'
err_logger.info(msg)
except ObjectDoesNotExist:
pass
return email
def clean(self):
super().clean()
err_logger = logging.getLogger('seminar.prihlaska.problem')
data = self.cleaned_data
if data.get('stat') != 'other' and data.get('stat_text') != '':
self.add_error('stat',forms.ValidationError('Nelze mít vybraný stát z menu a zároven zapsaný textem'))
if data.get('skola') and (data.get('skola_nazev') or data.get('skola_adresa')):
self.add_error('skola',forms.ValidationError('Pokud je škola v seznamu, nevypisujte ji ručně, pokud není, zrušte výběr ze seznamu (křížek vpravo)'))
if not data.get('skola'):
if data.get('skola_nazev')=='' and data.get('skola_adresa')=='':
self.add_error('skola',forms.ValidationError('Je nutné vyplnit školu'))
elif data.get('skola_nazev')=='':
self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy'))
elif data.get('skola_adresa')=='':
self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy'))
class ProfileEditForm(forms.Form):
username = forms.CharField(label='Přihlašovací jméno',
max_length=256,
required=False,
disabled=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)
skola = forms.ModelChoiceField(label="Škola",
queryset=Skola.objects.all(),
widget=autocomplete.ModelSelect2(
url='autocomplete_skola',
attrs = {'data-placeholder--id': '-1',
'data-placeholder--text' : '---',
'data-allow-clear': 'true'})
,required=False)
skola_nazev = forms.CharField(label='Název školy', max_length=256, required=False)
skola_adresa = forms.CharField(label='Adresa školy', max_length=256, required=False)
# trida = forms.CharField(label='Třída',max_length=10, required=True)
rok_maturity = forms.IntegerField(
label='Rok maturity',
min_value=date.today().year,
max_value=date.today().year+8,
required=True)
zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True)
zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat email s upozorněním na vydání nového čísla', required=False)
spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False)
# def clean_username(self):
# err_logger = logging.getLogger('seminar.prihlaska.problem')
# username = self.cleaned_data.get('username')
# try:
# User.objects.get(username=username)
# msg = "Username {} exists".format(username)
# err_logger.info(msg)
# raise forms.ValidationError('Přihlašovací jméno je již použito')
#
# except ObjectDoesNotExist:
# pass
# return username
#
def clean_email(self):
err_logger = logging.getLogger('seminar.prihlaska.problem')
email = self.cleaned_data.get('email')
try:
Osoba.objects.exclude(user__username=self.username).get(email=email)
msg = "Email {} exists (in edit)".format(email)
err_logger.info(msg)
raise forms.ValidationError('Email je již použit')
except ObjectDoesNotExist:
pass
return email
#def clean(self):
# super().clean()
#
# err_logger = logging.getLogger('seminar.prihlaska.problem')
# data = self.cleaned_data
# if data.get('password') != data.get('password_check'):
# self.add_error('password_check',forms.ValidationError('Hesla se neshodují'))
# if data.get('stat') != '' and data.get('stat_text') != '':
# self.add_error('stat',forms.ValidationError('Nelze mít vybraný stát z menu a zároven zapsaný textem'))
# if data.get('skola') and (data.get('skola_nazev') or data.get('skola_adresa')):
# self.add_error('skola',forms.ValidationError('Pokud je škola v seznamu, nevypisujte ji ručně, pokud není, zrušte výběr ze seznamu (křížek vpravo)'))
# if not data.get('skola'):
# if data.get('skola_nazev')=='' and data.get('skola_adresa')=='':
# self.add_error('skola',forms.ValidationError('Je nutné vyplnit školu'))
# elif data.get('skola_nazev')=='':
# self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy'))
# elif data.get('skola_adresa')=='':
# self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy'))
class PoMaturiteProfileEditForm(ProfileEditForm):
rok_maturity = forms.IntegerField(
label='Rok maturity',
required=True)
class NahrajObrazekKTreeNoduForm(forms.ModelForm): class NahrajObrazekKTreeNoduForm(forms.ModelForm):
class Meta: class Meta:

2
seminar/models/__init__.py

@ -1,2 +1,4 @@
from .models_all import * from .models_all import *
from .odevzdavatko import * from .odevzdavatko import *
from .base import *
from .personalni import *

22
seminar/models/base.py

@ -0,0 +1,22 @@
from django.urls import reverse
from django.db import models
class SeminarModelBase(models.Model):
class Meta:
abstract = True
def verejne(self):
return False
# def get_absolute_url(self):
# return "https://" + str(get_current_site(None)) + self.verejne_url()
def admin_url(self):
model_name = self.__class__.__name__.lower()
return reverse('admin:seminar_{}_change'.format(model_name), args=(self.id, ))
# def verejne_url(self):
# return None

476
seminar/models/models_all.py

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os import os
import random
import subprocess import subprocess
import pathlib import pathlib
import tempfile import tempfile
@ -8,21 +7,17 @@ import logging
from django.contrib.sites.shortcuts import get_current_site from django.contrib.sites.shortcuts import get_current_site
from django.db import models from django.db import models
from django.contrib import auth
from django.utils import timezone from django.utils import timezone
from django.conf import settings from django.conf import settings
from django.utils.encoding import force_text from django.urls import reverse
from django.utils.text import slugify
from django.urls import reverse, reverse_lazy
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils.text import get_valid_filename from django.utils.text import get_valid_filename
from imagekit.models import ImageSpecField, ProcessedImageField from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFit, Transpose from imagekit.processors import ResizeToFit
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django_countries.fields import CountryField
from solo.models import SingletonModel from solo.models import SingletonModel
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
@ -39,393 +34,11 @@ from polymorphic.models import PolymorphicModel
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from seminar.utils import aktivniResitele from seminar.utils import aktivniResitele
logger = logging.getLogger(__name__) from . import personalni as pm
class SeminarModelBase(models.Model):
class Meta:
abstract = True
def verejne(self):
return False
# def get_absolute_url(self):
# return "https://" + str(get_current_site(None)) + self.verejne_url()
def admin_url(self):
model_name = self.__class__.__name__.lower()
return reverse('admin:seminar_{}_change'.format(model_name), args=(self.id, ))
# def verejne_url(self):
# return None
@reversion.register(ignore_duplicates=True)
class Osoba(SeminarModelBase):
class Meta:
db_table = 'seminar_osoby'
verbose_name = 'Osoba'
verbose_name_plural = 'Osoby'
ordering = ['prijmeni','jmeno']
id = models.AutoField(primary_key = True)
jmeno = models.CharField('jméno', max_length=256)
prijmeni = models.CharField('příjmení', max_length=256)
prezdivka = models.CharField('přezdívka', blank=True, null=True, max_length=256)
# User, pokud má na webu účet
user = models.OneToOneField(settings.AUTH_USER_MODEL, blank=True, null=True,
verbose_name='uživatel', on_delete=models.DO_NOTHING)
# Pohlaví. Že ho neznáme se snad nestane (a ušetří to práci při programování)
pohlavi_muz = models.BooleanField('pohlaví (muž)', default=False)
email = models.EmailField('e-mail', max_length=256, blank=True, default='')
telefon = models.CharField('telefon', max_length=256, blank=True, default='')
datum_narozeni = models.DateField('datum narození', blank=True, null=True)
# NULL dokud nedali souhlas
datum_souhlasu_udaje = models.DateField('datum souhlasu (údaje)', blank=True, null=True,
help_text='Datum souhlasu se zpracováním osobních údajů')
# NULL dokud nedali souhlas
datum_souhlasu_zasilani = models.DateField('datum souhlasu (spam)', blank=True, null=True,
help_text='Datum souhlasu se zasíláním MFF materiálů')
# Alespoň odhad (rok či i měsíc)
datum_registrace = models.DateField('datum registrace do semináře', default=timezone.now)
# Ulice může být i jen číslo
ulice = models.CharField('ulice', max_length=256, blank=True, default='')
mesto = models.CharField('město', max_length=256, blank=True, default='')
psc = models.CharField('PSČ', max_length=32, blank=True, default='')
# ISO 3166-1 dvojznakovy kod zeme velkym pismem (CZ, SK)
# Ekvivalentní s CharField(max_length=2, default='CZ', ...)
stat = CountryField('stát', default='CZ',
help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)')
poznamka = models.TextField('neveřejná poznámka', blank=True,
help_text='Neveřejná poznámka k osobě (plain text)')
foto = ProcessedImageField(verbose_name='Fotografie osoby', from .base import SeminarModelBase
upload_to='image_osoby/velke/%Y/', null = True, blank = True,
help_text = 'Vlož fotografii osoby o libovolné velikosti',
processors=[
Transpose(Transpose.AUTO),
ResizeToFit(500, 500, upscale=False)
],
options={'quality': 95})
foto_male = ImageSpecField(source='foto',
processors=[
ResizeToFit(200, 200, upscale=False)
],
options={'quality': 95})
# má OneToOneField nejvýše s:
# Resitel
# Prijemce
# Organizator
def plne_jmeno(self):
return '{} {}'.format(self.jmeno, self.prijmeni)
def inicial_krestni(self):
jmena = self.jmeno.split()
return " ".join(['{}.'.format(jmeno[0]) for jmeno in jmena])
def __str__(self):
return self.plne_jmeno()
# Overridujeme save Osoby, aby když si změní e-mail, aby se projevil i v
# Userovi (a tak se dal poslat mail s resetem hesla)
def save(self, *args, **kwargs):
if self.user is not None:
u = self.user
# U svatého tučňáka, prosím ať tohle funguje.
# (Takhle se kódit asi nemá...)
u.email = self.email
u.save()
super().save()
#
# Mělo by být částečně vytaženo z Aesopa
# viz https://ovvp.mff.cuni.cz/wiki/aesop/export-skol.
#
@reversion.register(ignore_duplicates=True)
class Skola(SeminarModelBase):
class Meta:
db_table = 'seminar_skoly'
verbose_name = 'Škola'
verbose_name_plural = 'Školy'
ordering = ['mesto', 'nazev']
# Interní ID
id = models.AutoField(primary_key = True)
# Aesopi ID "izo:..." nebo "aesop:..."
# NULL znamená v exportu do aesopa "ufo"
aesop_id = models.CharField('Aesop ID', max_length=32, blank=True, default='',
help_text='Aesopi ID typu "izo:..." nebo "aesop:..."')
# IZO školy (jen české školy)
izo = models.CharField('IZO', max_length=32, blank=True,
help_text='IZO školy (jen české školy)')
# Celý název školy
nazev = models.CharField('název', max_length=256,
help_text='Celý název školy')
# Zkraceny nazev pro zobrazení ve výsledkovce, volitelné.
# Není v Aesopovi, musíme vytvářet sami.
kratky_nazev = models.CharField('zkrácený název', max_length=256, blank=True,
help_text="Zkrácený název pro zobrazení ve výsledkovce")
# Ulice může být jen číslo
ulice = models.CharField('ulice', max_length=256)
mesto = models.CharField('město', max_length=256)
psc = models.CharField('PSČ', max_length=32)
# ISO 3166-1 dvojznakovy kod zeme velkym pismem (CZ, SK)
# Ekvivalentní s CharField(max_length=2, default='CZ', ...)
stat = CountryField('stát', default='CZ',
help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)')
# Jaké vzdělání škpla poskytuje?
je_zs = models.BooleanField('základní stupeň', default=True)
je_ss = models.BooleanField('střední stupeň', default=True)
poznamka = models.TextField('neveřejná poznámka', blank=True,
help_text='Neveřejná poznámka ke škole (plain text)')
kontaktni_osoba = models.ForeignKey(Osoba, verbose_name='Kontaktní osoba',
blank=True, null=True, on_delete=models.SET_NULL)
def __str__(self):
return '{}, {}, {}'.format(self.nazev, self.ulice, self.mesto)
class Prijemce(SeminarModelBase):
class Meta:
db_table = 'seminar_prijemce'
verbose_name = 'příjemce'
verbose_name_plural = 'příjemce'
# Interní ID
id = models.AutoField(primary_key = True)
poznamka = models.TextField('neveřejná poznámka', blank=True,
help_text='Neveřejná poznámka k příemci čísel (plain text)')
osoba = models.OneToOneField(Osoba, verbose_name='komu', blank=False, null=False,
help_text='Které osobě či na jakou adresu se mají zasílat čísla',
on_delete=models.CASCADE)
# FIXME: možná chceme něco jako vazbu na osobu XOR školu a počet kusů k zaslání
# FIXME: a možná taky posílání na mail a možná taky přes něj chceme posílat i řešitelům
def __str__(self):
return self.osoba.plne_jmeno()
@reversion.register(ignore_duplicates=True)
class Resitel(SeminarModelBase):
class Meta:
db_table = 'seminar_resitele'
verbose_name = 'Řešitel'
verbose_name_plural = 'Řešitelé'
ordering = ['osoba']
# Interní ID
id = models.AutoField(primary_key = True)
osoba = models.OneToOneField(Osoba, blank=False, null=False, verbose_name='osoba',
on_delete=models.PROTECT)
skola = models.ForeignKey(Skola, blank=True, null=True, verbose_name='škola',
on_delete=models.SET_NULL)
# Očekávaný rok maturity a vyřazení z aktivních řešitelů
rok_maturity = models.IntegerField('rok maturity', blank=True, null=True)
ZASILAT_DOMU = 'domu'
ZASILAT_DO_SKOLY = 'do_skoly'
ZASILAT_NIKAM = 'nikam'
ZASILAT_CHOICES = [
(ZASILAT_DOMU, 'Domů'),
(ZASILAT_DO_SKOLY, 'Do školy'),
(ZASILAT_NIKAM, 'Nikam'),
]
zasilat = models.CharField('kam zasílat', max_length=32, choices=ZASILAT_CHOICES, blank=False, default=ZASILAT_DOMU)
zasilat_cislo_emailem = models.BooleanField('zasílat číslo emailem', help_text='True pokud chce řešitel dostávat číslo emailem', default=False)
poznamka = models.TextField('neveřejná poznámka', blank=True,
help_text='Neveřejná poznámka k řešiteli (plain text)')
def export_row(self):
"Slovnik pro pouziti v AESOP exportu"
return {
'id': self.id,
'name': self.osoba.jmeno,
'surname': self.osoba.prijmeni,
'gender': 'M' if self.osoba.pohlavi_muz else 'F',
'born': self.osoba.datum_narozeni.isoformat() if self.osoba.datum_narozeni else '',
'email': self.osoba.email,
'end-year': self.rok_maturity or '',
'street': self.osoba.ulice,
'town': self.osoba.mesto,
'postcode': self.osoba.psc,
'country': self.osoba.stat,
'spam-flag': 'Y' if self.osoba.datum_souhlasu_zasilani else '',
'spam-date': self.osoba.datum_souhlasu_zasilani.isoformat() if self.osoba.datum_souhlasu_zasilani else '',
'school': self.skola.aesop_id if self.skola else '',
'school-name': str(self.skola) if self.skola else 'Skola neni znama',
}
def rocnik(self, rocnik):
"""Vrati skolni rocnik resitele pro zadany Rocnik.
Vraci '' pro neznamy rok maturity resitele, Z* pro ekvivalent ."""
if self.rok_maturity is None:
return ''
rozdil = 5 - (self.rok_maturity - rocnik.prvni_rok)
if rozdil >= 1:
return str(rozdil)
else:
return 'Z' + str(rozdil + 9)
def vsechny_body(self):
"Spočítá body odjakživa."
vsechna_reseni = self.reseni_set.all()
from .odevzdavatko import Hodnoceni
vsechna_hodnoceni = Hodnoceni.objects.filter(
reseni__in=vsechna_reseni)
return sum(h.body for h in list(vsechna_hodnoceni) if h.body is not None)
def get_titul(self, body=None):
"Vrati titul jako řetězec."
# Nejprve si zadefinujeme titul
from enum import Enum
from functools import total_ordering
@total_ordering
class Titul(Enum):
""" Třída reprezentující možné tituly. Hodnoty jsou dvojice (dolní hranice, stringifikace). """
nic = (0, '')
bc = (20, 'Bc.')
mgr = (50, 'Mgr.')
dr = (100, 'Dr.')
doc = (200, 'Doc.')
prof = (500, 'Prof.')
akad = (1000, 'Akad.')
def __lt__(self, other):
return True if self.value[0] < other.value[0] else False
def __eq__(self, other): # Měla by být implicitní, ale klidně explicitně.
return True if self.value[0] == other.value[0] else False
def __str__(self):
return self.value[1]
@classmethod
def z_bodu(cls, body):
aktualni = cls.nic
# TODO: ověřit, že to funguje
for titul in cls: # Kdyžtak použít __members__.items()
if titul.value[0] <= body:
aktualni = titul
else:
break
return aktualni
# Hledáme body v databázi
# V listopadu 2020 jsme se na filosofické schůzce shodli o změně hranic titulů:
# - body z 25. ročníku a dříve byly shledány dvakrát hodnotnějšími
# - proto se započítávají dvojnásobně a byly posunuté hranice titulů
# - staré tituly se ale nemají odebrat, pokud řešitel v t.č. minulém (26.) ročníku měl titul, má ho mít pořád.
from .odevzdavatko import Hodnoceni
hodnoceni_do_25_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=25,reseni__in=self.reseni_set.all())
novejsi_hodnoceni = Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()).difference(hodnoceni_do_25_rocniku)
def body_z_hodnoceni(hh : list):
return sum(h.body for h in hh if h.body is not None)
stare_body = body_z_hodnoceni(hodnoceni_do_25_rocniku)
if body is None:
nove_body = body_z_hodnoceni(novejsi_hodnoceni)
else:
# Zjistíme, kolik bodů jsou staré, tedy hodnotnější
nove_body = max(0, body - stare_body) # Všechny body nad počet původních hodnotnějších
stare_body = min(stare_body, body) # Skutečný počet hodnotnějších bodů
logicke_body = 2*stare_body + nove_body
# Titul se určí následovně:
# - Pokud se řeší body, které jsou starší, než do 26 ročníku (včetně), dáváme tituly postaru.
# - Jinak dáváme tituly po novu...
# - ... ale titul se nesmí odebrat, pokud se zmenšil.
def titul_do_26_rocniku(body):
""" Původní hranice bodů za tituly """
if body < 10:
return Titul.nic
elif body < 20:
return Titul.bc
elif body < 50:
return Titul.mgr
elif body < 100:
return Titul.dr
elif body < 200:
return Titul.doc
elif body < 500:
return Titul.prof
else:
return Titul.akad
from .odevzdavatko import Hodnoceni
hodnoceni_do_26_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=26,reseni__in=self.reseni_set.all())
novejsi_body = body_z_hodnoceni(
Hodnoceni.objects.filter(reseni__in=self.reseni_set.all())
.difference(hodnoceni_do_26_rocniku)
)
starsi_body = body_z_hodnoceni(hodnoceni_do_26_rocniku)
if body is not None:
# Ještě z toho vybereme ty správně staré body
novejsi_body = max(0, body - starsi_body)
starsi_body = min(starsi_body, body)
# Titul pro 26. ročník
stary_titul = titul_do_26_rocniku(starsi_body)
# Titul podle aktuálních pravidel
novy_titul = Titul.z_bodu(logicke_body)
if novejsi_body == 0:
# Žádné nové body -- titul podle starých pravidel
return str(stary_titul)
return str(max(novy_titul, stary_titul))
def __str__(self):
return self.osoba.plne_jmeno()
logger = logging.getLogger(__name__)
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
@ -703,59 +316,6 @@ class Cislo(SeminarModelBase):
if self.datum_deadline_soustredeni is not None and self.datum_deadline_soustredeni > self.datum_deadline: if self.datum_deadline_soustredeni is not None and self.datum_deadline_soustredeni > self.datum_deadline:
raise ValidationError({'datum_deadline_soustredeni': "Soustřeďkový deadline musí předcházet finálnímu deadlinu"}) raise ValidationError({'datum_deadline_soustredeni': "Soustřeďkový deadline musí předcházet finálnímu deadlinu"})
@reversion.register(ignore_duplicates=True)
class Organizator(SeminarModelBase):
# zmena dedicnosti z models.Model na SeminarModelBase, potencialni vznik bugu
osoba = models.OneToOneField(Osoba, verbose_name='osoba', related_name='org',
help_text='osobní údaje organizátora', null=False, blank=False,
on_delete=models.PROTECT)
vytvoreno = models.DateTimeField(
'Vytvořeno',
default=timezone.now,
blank=True,
editable=False
)
organizuje_od = models.DateTimeField('Organizuje od', blank=True, null=True)
organizuje_do = models.DateTimeField('Organizuje do', blank=True, null=True)
studuje = models.CharField('Studium aj.', max_length = 256,
null = True, blank = True,
help_text="Např. 'Studuje Obecnou fyziku (Bc.), 3. ročník', "
"'Vystudovala Diskrétní modely a algoritmy (Mgr.)' nebo "
"'Přednáší na MFF'")
strucny_popis_organizatora = models.TextField('Stručný popis organizátora',
null = True, blank = True)
skola = models.CharField('Škola, kterou studuje', max_length = 256, null=True, blank=True,
help_text="Škola, např. MFF, VŠCHT, VUT, ... prostě aby se nemuselo psát do studuje"
"školu, ale jen obor, možnost zobrazit zvlášť")
def clean(self):
if self.organizuje_od and self.organizuje_do and (self.organizuje_od > self.organizuje_do):
raise ValidationError("Organizátor nemůže skončit s organizováním dříve než začal!")
super().clean()
def __str__(self):
if self.osoba.prezdivka:
return "{} '{}' {}".format(self.osoba.jmeno,
self.osoba.prezdivka,
self.osoba.prijmeni)
else:
return "{} {}".format(self.osoba.jmeno, self.osoba.prijmeni)
class Meta:
verbose_name = 'Organizátor'
verbose_name_plural = 'Organizátoři'
# Řadí aktivní orgy na začátek, pod tím v pořadí od nejstarších neaktivní orgy.
# TODO: Chtěl bych spíš mít nejstarší orgy dole.
# TODO: Zohledňovat přezdívky?
# TODO: Sjednotit s tím, jak se řadí organizátoři v seznau orgů na webu
ordering = ['-organizuje_do', 'osoba__jmeno', 'osoba__prijmeni']
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
class Soustredeni(SeminarModelBase): class Soustredeni(SeminarModelBase):
@ -783,10 +343,10 @@ class Soustredeni(SeminarModelBase):
misto = models.CharField('místo soustředění', max_length=256, blank=True, default='', misto = models.CharField('místo soustředění', max_length=256, blank=True, default='',
help_text='Místo (název obce, volitelně též objektu') help_text='Místo (název obce, volitelně též objektu')
ucastnici = models.ManyToManyField(Resitel, verbose_name='účastníci soustředění', ucastnici = models.ManyToManyField(pm.Resitel, verbose_name='účastníci soustředění',
help_text='Seznam účastníků soustředění', through='Soustredeni_Ucastnici') help_text='Seznam účastníků soustředění', through='Soustredeni_Ucastnici')
organizatori = models.ManyToManyField(Organizator, organizatori = models.ManyToManyField(pm.Organizator,
verbose_name='Organizátoři soustředění', verbose_name='Organizátoři soustředění',
help_text='Seznam organizátorů soustředění', help_text='Seznam organizátorů soustředění',
through='Soustredeni_Organizatori') through='Soustredeni_Organizatori')
@ -865,15 +425,15 @@ class Problem(SeminarModelBase,PolymorphicModel):
poznamka = models.TextField('org poznámky (HTML)', blank=True, poznamka = models.TextField('org poznámky (HTML)', blank=True,
help_text='Neveřejný návrh úlohy, návrh řešení, text zadání, poznámky ...') help_text='Neveřejný návrh úlohy, návrh řešení, text zadání, poznámky ...')
autor = models.ForeignKey(Organizator, verbose_name='autor problému', autor = models.ForeignKey(pm.Organizator, verbose_name='autor problému',
related_name='autor_problemu_%(class)s', null=True, blank=True, related_name='autor_problemu_%(class)s', null=True, blank=True,
on_delete=models.SET_NULL) on_delete=models.SET_NULL)
garant = models.ForeignKey(Organizator, verbose_name='garant zadaného problému', garant = models.ForeignKey(pm.Organizator, verbose_name='garant zadaného problému',
related_name='garant_problemu_%(class)s', null=True, blank=True, related_name='garant_problemu_%(class)s', null=True, blank=True,
on_delete=models.SET_NULL) on_delete=models.SET_NULL)
opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé', opravovatele = models.ManyToManyField(pm.Organizator, verbose_name='opravovatelé',
blank=True, related_name='opravovatele_%(class)s') blank=True, related_name='opravovatele_%(class)s')
kod = models.CharField('lokální kód', max_length=32, blank=True, default='', kod = models.CharField('lokální kód', max_length=32, blank=True, default='',
@ -1126,7 +686,7 @@ class Pohadka(SeminarModelBase):
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
autor = models.ForeignKey( autor = models.ForeignKey(
Organizator, pm.Organizator,
verbose_name="Autor pohádky", verbose_name="Autor pohádky",
# Při nahrávání z TeXu není vyplnění vyžadováno, v adminu je # Při nahrávání z TeXu není vyplnění vyžadováno, v adminu je
@ -1171,7 +731,7 @@ class Soustredeni_Ucastnici(SeminarModelBase):
# Interní ID # Interní ID
id = models.AutoField(primary_key = True) id = models.AutoField(primary_key = True)
resitel = models.ForeignKey(Resitel, verbose_name='řešitel', on_delete=models.PROTECT) resitel = models.ForeignKey(pm.Resitel, verbose_name='řešitel', on_delete=models.PROTECT)
soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění', soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění',
on_delete=models.PROTECT) on_delete=models.PROTECT)
@ -1196,7 +756,7 @@ class Soustredeni_Organizatori(SeminarModelBase):
# Interní ID # Interní ID
id = models.AutoField(primary_key = True) id = models.AutoField(primary_key = True)
organizator = models.ForeignKey(Organizator, verbose_name='organizátor', organizator = models.ForeignKey(pm.Organizator, verbose_name='organizátor',
on_delete=models.PROTECT) on_delete=models.PROTECT)
soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění', soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění',
@ -1225,7 +785,7 @@ class Konfera(Problem):
help_text='Abstrakt konfery tak, jak byl uveden ve sborníku') help_text='Abstrakt konfery tak, jak byl uveden ve sborníku')
# FIXME: Umíme omezit jen na účastníky daného soustřeďka? # FIXME: Umíme omezit jen na účastníky daného soustřeďka?
ucastnici = models.ManyToManyField(Resitel, verbose_name='účastníci konfery', ucastnici = models.ManyToManyField(pm.Resitel, verbose_name='účastníci konfery',
help_text='Seznam účastníků konfery', through='Konfery_Ucastnici') help_text='Seznam účastníků konfery', through='Konfery_Ucastnici')
soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění', soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění',
@ -1266,7 +826,7 @@ class Konfery_Ucastnici(models.Model):
# Interní ID # Interní ID
id = models.AutoField(primary_key = True) id = models.AutoField(primary_key = True)
resitel = models.ForeignKey(Resitel, verbose_name='řešitel', on_delete=models.PROTECT) resitel = models.ForeignKey(pm.Resitel, verbose_name='řešitel', on_delete=models.PROTECT)
konfera = models.ForeignKey(Konfera, verbose_name='konfera', on_delete=models.CASCADE) konfera = models.ForeignKey(Konfera, verbose_name='konfera', on_delete=models.CASCADE)
@ -1452,7 +1012,7 @@ class OrgTextNode(TreeNode):
verbose_name = 'Organizátorský článek (Node)' verbose_name = 'Organizátorský článek (Node)'
verbose_name_plural = 'Organizátorské články (Node)' verbose_name_plural = 'Organizátorské články (Node)'
organizator = models.ForeignKey(Organizator, organizator = models.ForeignKey(pm.Organizator,
null=False, null=False,
blank=False, blank=False,
on_delete=models.DO_NOTHING, on_delete=models.DO_NOTHING,
@ -1598,7 +1158,7 @@ class Novinky(models.Model):
], ],
options={'quality': 95}) options={'quality': 95})
autor = models.ForeignKey(Organizator, verbose_name='Autor novinky', null=True, autor = models.ForeignKey(pm.Organizator, verbose_name='Autor novinky', null=True,
on_delete=models.SET_NULL) on_delete=models.SET_NULL)
zverejneno = models.BooleanField('Zveřejněno', default=False) zverejneno = models.BooleanField('Zveřejněno', default=False)

6
seminar/models/odevzdavatko.py

@ -10,6 +10,8 @@ from django.utils import timezone
from django.conf import settings from django.conf import settings
from seminar.models import models_all as am from seminar.models import models_all as am
from seminar.models import personalni as pm
from seminar.models.base import SeminarModelBase
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
@ -29,7 +31,7 @@ class Reseni(am.SeminarModelBase):
problem = models.ManyToManyField(am.Problem, verbose_name='problém', help_text='Problém', problem = models.ManyToManyField(am.Problem, verbose_name='problém', help_text='Problém',
through='Hodnoceni') through='Hodnoceni')
resitele = models.ManyToManyField(am.Resitel, verbose_name='autoři řešení', resitele = models.ManyToManyField(pm.Resitel, verbose_name='autoři řešení',
help_text='Seznam autorů řešení', through='Reseni_Resitele') help_text='Seznam autorů řešení', through='Reseni_Resitele')
@ -160,7 +162,7 @@ class Reseni_Resitele(models.Model):
# Interní ID # Interní ID
id = models.AutoField(primary_key = True) id = models.AutoField(primary_key = True)
resitele = models.ForeignKey(am.Resitel, verbose_name='řešitel', on_delete=models.PROTECT) resitele = models.ForeignKey(pm.Resitel, verbose_name='řešitel', on_delete=models.PROTECT)
reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE)

438
seminar/models/personalni.py

@ -0,0 +1,438 @@
# -*- coding: utf-8 -*-
import logging
from django.db import models
from django.utils import timezone
from django.conf import settings
from django.core.exceptions import ValidationError
from imagekit.models import ImageSpecField, ProcessedImageField
from imagekit.processors import ResizeToFit, Transpose
from django_countries.fields import CountryField
from reversion import revisions as reversion
from .base import SeminarModelBase
logger = logging.getLogger(__name__)
@reversion.register(ignore_duplicates=True)
class Osoba(SeminarModelBase):
class Meta:
db_table = 'seminar_osoby'
verbose_name = 'Osoba'
verbose_name_plural = 'Osoby'
ordering = ['prijmeni','jmeno']
id = models.AutoField(primary_key = True)
jmeno = models.CharField('jméno', max_length=256)
prijmeni = models.CharField('příjmení', max_length=256)
prezdivka = models.CharField('přezdívka', blank=True, null=True, max_length=256)
# User, pokud má na webu účet
user = models.OneToOneField(settings.AUTH_USER_MODEL, blank=True, null=True,
verbose_name='uživatel', on_delete=models.DO_NOTHING)
# Pohlaví. Že ho neznáme se snad nestane (a ušetří to práci při programování)
pohlavi_muz = models.BooleanField('pohlaví (muž)', default=False)
email = models.EmailField('e-mail', max_length=256, blank=True, default='')
telefon = models.CharField('telefon', max_length=256, blank=True, default='')
datum_narozeni = models.DateField('datum narození', blank=True, null=True)
# NULL dokud nedali souhlas
datum_souhlasu_udaje = models.DateField('datum souhlasu (údaje)', blank=True, null=True,
help_text='Datum souhlasu se zpracováním osobních údajů')
# NULL dokud nedali souhlas
datum_souhlasu_zasilani = models.DateField('datum souhlasu (spam)', blank=True, null=True,
help_text='Datum souhlasu se zasíláním MFF materiálů')
# Alespoň odhad (rok či i měsíc)
datum_registrace = models.DateField('datum registrace do semináře', default=timezone.now)
# Ulice může být i jen číslo
ulice = models.CharField('ulice', max_length=256, blank=True, default='')
mesto = models.CharField('město', max_length=256, blank=True, default='')
psc = models.CharField('PSČ', max_length=32, blank=True, default='')
# ISO 3166-1 dvojznakovy kod zeme velkym pismem (CZ, SK)
# Ekvivalentní s CharField(max_length=2, default='CZ', ...)
stat = CountryField('stát', default='CZ',
help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)')
poznamka = models.TextField('neveřejná poznámka', blank=True,
help_text='Neveřejná poznámka k osobě (plain text)')
foto = ProcessedImageField(verbose_name='Fotografie osoby',
upload_to='image_osoby/velke/%Y/', null = True, blank = True,
help_text = 'Vlož fotografii osoby o libovolné velikosti',
processors=[
Transpose(Transpose.AUTO),
ResizeToFit(500, 500, upscale=False)
],
options={'quality': 95})
foto_male = ImageSpecField(source='foto',
processors=[
ResizeToFit(200, 200, upscale=False)
],
options={'quality': 95})
# má OneToOneField nejvýše s:
# Resitel
# Prijemce
# Organizator
def plne_jmeno(self):
return '{} {}'.format(self.jmeno, self.prijmeni)
def inicial_krestni(self):
jmena = self.jmeno.split()
return " ".join(['{}.'.format(jmeno[0]) for jmeno in jmena])
def __str__(self):
return self.plne_jmeno()
# Overridujeme save Osoby, aby když si změní e-mail, aby se projevil i v
# Userovi (a tak se dal poslat mail s resetem hesla)
def save(self, *args, **kwargs):
if self.user is not None:
u = self.user
# U svatého tučňáka, prosím ať tohle funguje.
# (Takhle se kódit asi nemá...)
u.email = self.email
u.save()
super().save()
#
# Mělo by být částečně vytaženo z Aesopa
# viz https://ovvp.mff.cuni.cz/wiki/aesop/export-skol.
#
@reversion.register(ignore_duplicates=True)
class Skola(SeminarModelBase):
class Meta:
db_table = 'seminar_skoly'
verbose_name = 'Škola'
verbose_name_plural = 'Školy'
ordering = ['mesto', 'nazev']
# Interní ID
id = models.AutoField(primary_key = True)
# Aesopi ID "izo:..." nebo "aesop:..."
# NULL znamená v exportu do aesopa "ufo"
aesop_id = models.CharField('Aesop ID', max_length=32, blank=True, default='',
help_text='Aesopi ID typu "izo:..." nebo "aesop:..."')
# IZO školy (jen české školy)
izo = models.CharField('IZO', max_length=32, blank=True,
help_text='IZO školy (jen české školy)')
# Celý název školy
nazev = models.CharField('název', max_length=256,
help_text='Celý název školy')
# Zkraceny nazev pro zobrazení ve výsledkovce, volitelné.
# Není v Aesopovi, musíme vytvářet sami.
kratky_nazev = models.CharField('zkrácený název', max_length=256, blank=True,
help_text="Zkrácený název pro zobrazení ve výsledkovce")
# Ulice může být jen číslo
ulice = models.CharField('ulice', max_length=256)
mesto = models.CharField('město', max_length=256)
psc = models.CharField('PSČ', max_length=32)
# ISO 3166-1 dvojznakovy kod zeme velkym pismem (CZ, SK)
# Ekvivalentní s CharField(max_length=2, default='CZ', ...)
stat = CountryField('stát', default='CZ',
help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)')
# Jaké vzdělání škpla poskytuje?
je_zs = models.BooleanField('základní stupeň', default=True)
je_ss = models.BooleanField('střední stupeň', default=True)
poznamka = models.TextField('neveřejná poznámka', blank=True,
help_text='Neveřejná poznámka ke škole (plain text)')
kontaktni_osoba = models.ForeignKey(Osoba, verbose_name='Kontaktní osoba',
blank=True, null=True, on_delete=models.SET_NULL)
def __str__(self):
return '{}, {}, {}'.format(self.nazev, self.ulice, self.mesto)
class Prijemce(SeminarModelBase):
class Meta:
db_table = 'seminar_prijemce'
verbose_name = 'příjemce'
verbose_name_plural = 'příjemce'
# Interní ID
id = models.AutoField(primary_key = True)
poznamka = models.TextField('neveřejná poznámka', blank=True,
help_text='Neveřejná poznámka k příemci čísel (plain text)')
osoba = models.OneToOneField(Osoba, verbose_name='komu', blank=False, null=False,
help_text='Které osobě či na jakou adresu se mají zasílat čísla',
on_delete=models.CASCADE)
# FIXME: možná chceme něco jako vazbu na osobu XOR školu a počet kusů k zaslání
# FIXME: a možná taky posílání na mail a možná taky přes něj chceme posílat i řešitelům
def __str__(self):
return self.osoba.plne_jmeno()
@reversion.register(ignore_duplicates=True)
class Resitel(SeminarModelBase):
class Meta:
db_table = 'seminar_resitele'
verbose_name = 'Řešitel'
verbose_name_plural = 'Řešitelé'
ordering = ['osoba']
# Interní ID
id = models.AutoField(primary_key = True)
osoba = models.OneToOneField(Osoba, blank=False, null=False, verbose_name='osoba',
on_delete=models.PROTECT)
skola = models.ForeignKey(Skola, blank=True, null=True, verbose_name='škola',
on_delete=models.SET_NULL)
# Očekávaný rok maturity a vyřazení z aktivních řešitelů
rok_maturity = models.IntegerField('rok maturity', blank=True, null=True)
ZASILAT_DOMU = 'domu'
ZASILAT_DO_SKOLY = 'do_skoly'
ZASILAT_NIKAM = 'nikam'
ZASILAT_CHOICES = [
(ZASILAT_DOMU, 'Domů'),
(ZASILAT_DO_SKOLY, 'Do školy'),
(ZASILAT_NIKAM, 'Nikam'),
]
zasilat = models.CharField('kam zasílat', max_length=32, choices=ZASILAT_CHOICES, blank=False, default=ZASILAT_DOMU)
zasilat_cislo_emailem = models.BooleanField('zasílat číslo emailem', help_text='True pokud chce řešitel dostávat číslo emailem', default=False)
poznamka = models.TextField('neveřejná poznámka', blank=True,
help_text='Neveřejná poznámka k řešiteli (plain text)')
def export_row(self):
"Slovnik pro pouziti v AESOP exportu"
return {
'id': self.id,
'name': self.osoba.jmeno,
'surname': self.osoba.prijmeni,
'gender': 'M' if self.osoba.pohlavi_muz else 'F',
'born': self.osoba.datum_narozeni.isoformat() if self.osoba.datum_narozeni else '',
'email': self.osoba.email,
'end-year': self.rok_maturity or '',
'street': self.osoba.ulice,
'town': self.osoba.mesto,
'postcode': self.osoba.psc,
'country': self.osoba.stat,
'spam-flag': 'Y' if self.osoba.datum_souhlasu_zasilani else '',
'spam-date': self.osoba.datum_souhlasu_zasilani.isoformat() if self.osoba.datum_souhlasu_zasilani else '',
'school': self.skola.aesop_id if self.skola else '',
'school-name': str(self.skola) if self.skola else 'Skola neni znama',
}
def rocnik(self, rocnik):
"""Vrati skolni rocnik resitele pro zadany Rocnik.
Vraci '' pro neznamy rok maturity resitele, Z* pro ekvivalent ."""
if self.rok_maturity is None:
return ''
rozdil = 5 - (self.rok_maturity - rocnik.prvni_rok)
if rozdil >= 1:
return str(rozdil)
else:
return 'Z' + str(rozdil + 9)
def vsechny_body(self):
"Spočítá body odjakživa."
vsechna_reseni = self.reseni_set.all()
from .odevzdavatko import Hodnoceni
vsechna_hodnoceni = Hodnoceni.objects.filter(
reseni__in=vsechna_reseni)
return sum(h.body for h in list(vsechna_hodnoceni) if h.body is not None)
def get_titul(self, body=None):
"Vrati titul jako řetězec."
# Nejprve si zadefinujeme titul
from enum import Enum
from functools import total_ordering
@total_ordering
class Titul(Enum):
""" Třída reprezentující možné tituly. Hodnoty jsou dvojice (dolní hranice, stringifikace). """
nic = (0, '')
bc = (20, 'Bc.')
mgr = (50, 'Mgr.')
dr = (100, 'Dr.')
doc = (200, 'Doc.')
prof = (500, 'Prof.')
akad = (1000, 'Akad.')
def __lt__(self, other):
return True if self.value[0] < other.value[0] else False
def __eq__(self, other): # Měla by být implicitní, ale klidně explicitně.
return True if self.value[0] == other.value[0] else False
def __str__(self):
return self.value[1]
@classmethod
def z_bodu(cls, body):
aktualni = cls.nic
# TODO: ověřit, že to funguje
for titul in cls: # Kdyžtak použít __members__.items()
if titul.value[0] <= body:
aktualni = titul
else:
break
return aktualni
# Hledáme body v databázi
# V listopadu 2020 jsme se na filosofické schůzce shodli o změně hranic titulů:
# - body z 25. ročníku a dříve byly shledány dvakrát hodnotnějšími
# - proto se započítávají dvojnásobně a byly posunuté hranice titulů
# - staré tituly se ale nemají odebrat, pokud řešitel v t.č. minulém (26.) ročníku měl titul, má ho mít pořád.
from .odevzdavatko import Hodnoceni
hodnoceni_do_25_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=25,reseni__in=self.reseni_set.all())
novejsi_hodnoceni = Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()).difference(hodnoceni_do_25_rocniku)
def body_z_hodnoceni(hh : list):
return sum(h.body for h in hh if h.body is not None)
stare_body = body_z_hodnoceni(hodnoceni_do_25_rocniku)
if body is None:
nove_body = body_z_hodnoceni(novejsi_hodnoceni)
else:
# Zjistíme, kolik bodů jsou staré, tedy hodnotnější
nove_body = max(0, body - stare_body) # Všechny body nad počet původních hodnotnějších
stare_body = min(stare_body, body) # Skutečný počet hodnotnějších bodů
logicke_body = 2*stare_body + nove_body
# Titul se určí následovně:
# - Pokud se řeší body, které jsou starší, než do 26 ročníku (včetně), dáváme tituly postaru.
# - Jinak dáváme tituly po novu...
# - ... ale titul se nesmí odebrat, pokud se zmenšil.
def titul_do_26_rocniku(body):
""" Původní hranice bodů za tituly """
if body < 10:
return Titul.nic
elif body < 20:
return Titul.bc
elif body < 50:
return Titul.mgr
elif body < 100:
return Titul.dr
elif body < 200:
return Titul.doc
elif body < 500:
return Titul.prof
else:
return Titul.akad
from .odevzdavatko import Hodnoceni
hodnoceni_do_26_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=26,reseni__in=self.reseni_set.all())
novejsi_body = body_z_hodnoceni(
Hodnoceni.objects.filter(reseni__in=self.reseni_set.all())
.difference(hodnoceni_do_26_rocniku)
)
starsi_body = body_z_hodnoceni(hodnoceni_do_26_rocniku)
if body is not None:
# Ještě z toho vybereme ty správně staré body
novejsi_body = max(0, body - starsi_body)
starsi_body = min(starsi_body, body)
# Titul pro 26. ročník
stary_titul = titul_do_26_rocniku(starsi_body)
# Titul podle aktuálních pravidel
novy_titul = Titul.z_bodu(logicke_body)
if novejsi_body == 0:
# Žádné nové body -- titul podle starých pravidel
return str(stary_titul)
return str(max(novy_titul, stary_titul))
def __str__(self):
return self.osoba.plne_jmeno()
@reversion.register(ignore_duplicates=True)
class Organizator(SeminarModelBase):
osoba = models.OneToOneField(Osoba, verbose_name='osoba', related_name='org',
help_text='osobní údaje organizátora', null=False, blank=False,
on_delete=models.PROTECT)
vytvoreno = models.DateTimeField(
'Vytvořeno',
default=timezone.now,
blank=True,
editable=False
)
organizuje_od = models.DateTimeField('Organizuje od', blank=True, null=True)
organizuje_do = models.DateTimeField('Organizuje do', blank=True, null=True)
studuje = models.CharField('Studium aj.', max_length = 256,
null = True, blank = True,
help_text="Např. 'Studuje Obecnou fyziku (Bc.), 3. ročník', "
"'Vystudovala Diskrétní modely a algoritmy (Mgr.)' nebo "
"'Přednáší na MFF'")
strucny_popis_organizatora = models.TextField('Stručný popis organizátora',
null = True, blank = True)
skola = models.CharField('Škola, kterou studuje', max_length = 256, null=True, blank=True,
help_text="Škola, např. MFF, VŠCHT, VUT, ... prostě aby se nemuselo psát do studuje"
"školu, ale jen obor, možnost zobrazit zvlášť")
def clean(self):
if self.organizuje_od and self.organizuje_do and (self.organizuje_od > self.organizuje_do):
raise ValidationError("Organizátor nemůže skončit s organizováním dříve než začal!")
super().clean()
def __str__(self):
if self.osoba.prezdivka:
return "{} '{}' {}".format(self.osoba.jmeno,
self.osoba.prezdivka,
self.osoba.prijmeni)
else:
return "{} {}".format(self.osoba.jmeno, self.osoba.prijmeni)
class Meta:
verbose_name = 'Organizátor'
verbose_name_plural = 'Organizátoři'
# Řadí aktivní orgy na začátek, pod tím v pořadí od nejstarších neaktivní orgy.
# TODO: Chtěl bych spíš mít nejstarší orgy dole.
# TODO: Zohledňovat přezdívky?
# TODO: Sjednotit s tím, jak se řadí organizátoři v seznau orgů na webu
ordering = ['-organizuje_do', 'osoba__jmeno', 'osoba__prijmeni']

105
seminar/templates/seminar/profil/edit.html

@ -1,105 +0,0 @@
{% extends "base.html" %}
{% load staticfiles %}
{% block script %}
<script src="{% static 'seminar/prihlaska.js' %}"></script>
{% endblock %}
<!--
# pro přidání políčka do formuláře je potřeba
# - mít v modelu tu položku, kterou chci upravovat
# - přidat do views (prihlaskaView, resitelEditView)
# - přidat do forms
# - includovat do html
-->
{% block content %}
<h1>
{% block nadpis1a %}{% block nadpis1b %}
Změna osobních údajů
{% endblock %}{% endblock %}
</h1>
<form action="{% url 'seminar_resitel_edit' %}" method="post">
{% csrf_token %}
{{form.non_field_errors}}
<hr>
<h4>
Přihlašovací údaje
</h4>
<table class="form">
{% include "seminar/profil/prihlaska_field.html" with field=form.username %}
</table>
<hr>
<h4>
Osobní údaje
</h4>
<table class="form">
{% include "seminar/profil/prihlaska_field.html" with field=form.jmeno %}
{% include "seminar/profil/prihlaska_field.html" with field=form.prijmeni %}
{% include "seminar/profil/prihlaska_field.html" with field=form.pohlavi_muz%}
{% include "seminar/profil/prihlaska_field.html" with field=form.email %}
{% include "seminar/profil/prihlaska_field.html" with field=form.telefon %}
{% include "seminar/profil/prihlaska_field.html" with field=form.datum_narozeni %}
</table>
<hr>
<h4>
Bydliště
</h4>
<table class="form">
{% include "seminar/profil/prihlaska_field.html" with field=form.ulice %}
{% include "seminar/profil/prihlaska_field.html" with field=form.mesto %}
{% include "seminar/profil/prihlaska_field.html" with field=form.psc %}
{% include "seminar/profil/prihlaska_field.html" with field=form.stat %}
{% include "seminar/profil/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%}
</table>
<hr>
<h4>
Škola
</h4>
<table class="form">
{% include "seminar/profil/prihlaska_field.html" with field=form.skola %}
<tr><td colspan="2" ><button id="id_skola_text_button" type="button">Škola není v seznamu</button></td></tr>
<tr><td id="id_li_skola_vypln" colspan="2">Vyplň prosím celý název a adresu školy.</td></tr>
{% include "seminar/profil/prihlaska_field.html" with field=form.skola_nazev id="id_li_skola_nazev" %}
{% include "seminar/profil/prihlaska_field.html" with field=form.skola_adresa id="id_li_skola_adresa" %}
{% include "seminar/profil/prihlaska_field.html" with field=form.rok_maturity %}
</table>
<hr>
<h4>
Pošta
</h4>
<table class="form">
{% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %}
{% include "seminar/profil/prihlaska_field.html" with field=form.zasilat_cislo_emailem %}
</table>
<hr>
<h4>
Zasílání propagačních materiálů
</h4>
<table class="form">
{% include "seminar/profil/prihlaska_field.html" with field=form.spam %}
</table>
<hr>
<input type="submit" value="Změnit">
</form>
<script>
$("#id_stat").on("change",addrCountryChanged);
$("#id_skola_text_button").on("click",schoolNotInList);
</script>
{% endblock %}

123
seminar/templates/seminar/profil/prihlaska.html

@ -1,123 +0,0 @@
{% extends "base.html" %}
{% load staticfiles %}
{% block script %}
<script src="{% static 'seminar/prihlaska.js' %}"></script>
{% endblock %}
<!--
# pro přidání políčka do formuláře je potřeba
# - mít v modelu tu položku, kterou chci upravovat
# - přidat do views (prihlaskaView, resitelEditView)
# - přidat do forms
# - includovat do html
-->
{% block content %}
<h1>
{% block nadpis1a %}{% block nadpis1b %}
Přihláška do semináře
{% endblock %}{% endblock %}
</h1>
<p><b>Tučně</b> popsaná pole jsou povinná.</p>
<form action="{% url 'seminar_prihlaska' %}" method="post">
{% csrf_token %}
{{form.non_field_errors}}
<hr>
<h4>
Přihlašovací údaje
</h4>
<table class="form">
{% include "seminar/profil/prihlaska_field.html" with field=form.username %}
{# {% include "seminar/profil/prihlaska_field.html" with field=form.password %}#}
{# {% include "seminar/profil/prihlaska_field.html" with field=form.password_check %}#}
</table>
<hr>
<h4>
Osobní údaje
</h4>
<table class="form">
{% include "seminar/profil/prihlaska_field.html" with field=form.jmeno %}
{% include "seminar/profil/prihlaska_field.html" with field=form.prijmeni %}
{% include "seminar/profil/prihlaska_field.html" with field=form.pohlavi_muz%}
{% include "seminar/profil/prihlaska_field.html" with field=form.email %}
{% include "seminar/profil/prihlaska_field.html" with field=form.telefon %}
{% include "seminar/profil/prihlaska_field.html" with field=form.datum_narozeni %}
</table>
<hr>
<h4>
Bydliště
</h4>
<table class="form">
{% include "seminar/profil/prihlaska_field.html" with field=form.ulice %}
{% include "seminar/profil/prihlaska_field.html" with field=form.mesto %}
{% include "seminar/profil/prihlaska_field.html" with field=form.psc %}
{% include "seminar/profil/prihlaska_field.html" with field=form.stat %}
{% include "seminar/profil/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%}
</table>
<hr>
<h4>
Škola
</h4>
<table class="form">
{% include "seminar/profil/prihlaska_field.html" with field=form.skola %}
<tr><td colspan="2" ><button id="id_skola_text_button" type="button">Škola není v seznamu</button></td></tr>
<tr><td id="id_li_skola_vypln" colspan="2">Vyplň prosím celý název a adresu školy.</td></tr>
{% include "seminar/profil/prihlaska_field.html" with field=form.skola_nazev id="id_li_skola_nazev" %}
{% include "seminar/profil/prihlaska_field.html" with field=form.skola_adresa id="id_li_skola_adresa" %}
{% include "seminar/profil/prihlaska_field.html" with field=form.rok_maturity %}
</table>
<hr>
<h4>
Pošta
</h4>
<table class="form">
{% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %}
{% include "seminar/profil/prihlaska_field.html" with field=form.zasilat_cislo_emailem %}
</table>
<hr>
<h4>
GDPR
</h4>
{% include "seminar/profil/gdpr.html" %}
<table class="form">
{% include "seminar/profil/prihlaska_field.html" with field=form.gdpr %}
</table>
<hr>
<h4>
Zasílání propagačních materiálů
</h4>
<table class="form">
{% include "seminar/profil/prihlaska_field.html" with field=form.spam %}
</table>
<hr>
<input type="submit" value="Odeslat">
</form>
<script>
$("#id_stat").on("change",addrCountryChanged);
$("#id_skola_text_button").on("click",schoolNotInList);
</script>
{% endblock %}

18
seminar/urls.py

@ -1,5 +1,4 @@
from django.urls import path, include, re_path from django.urls import path, include, re_path
from django.contrib.auth.decorators import login_required
from . import views from . import views
from .utils import org_required from .utils import org_required
@ -107,23 +106,6 @@ urlpatterns = [
org_required(views.soustredeniObalkyView), org_required(views.soustredeniObalkyView),
name='seminar_soustredeni_obalky' name='seminar_soustredeni_obalky'
), ),
# příprava na nestatický orgorozcestník
path(
'org/rozcestnik/',
org_required(views.OrgoRozcestnikView.as_view()),
name='seminar_org_rozcestnik'
),
path('prihlaska/',views.prihlaskaView, name='seminar_prihlaska'),
path(
'resitel/osobni-udaje/',
login_required(views.resitelEditView),
name='seminar_resitel_edit'
),
# Obecný view na profil -- orgům dá rozcestník, řešitelům jejich stránku
path('profil/', views.profilView, name='profil'),
re_path(r'^temp/vue/.*$',views.VueTestView.as_view(),name='vue_test_view'), re_path(r'^temp/vue/.*$',views.VueTestView.as_view(),name='vue_test_view'),
path('temp/image_upload/', views.NahrajObrazekKTreeNoduView.as_view()), path('temp/image_upload/', views.NahrajObrazekKTreeNoduView.as_view()),

288
seminar/views/views_all.py

@ -1,3 +1,4 @@
from django.forms import model_to_dict
from django.shortcuts import get_object_or_404, render, redirect from django.shortcuts import get_object_or_404, render, redirect
from django.http import HttpResponse, JsonResponse from django.http import HttpResponse, JsonResponse
from django.urls import reverse from django.urls import reverse
@ -6,20 +7,16 @@ from django.views import generic
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.http import Http404 from django.http import Http404
from django.db.models import Q, Sum, Count from django.db.models import Q, Sum, Count
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic.edit import CreateView from django.views.generic.edit import CreateView
from django.views.generic.base import TemplateView, RedirectView from django.views.generic.base import RedirectView
from django.contrib.auth.models import User, Permission, Group
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import transaction
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
import seminar.models as s import seminar.models as s
import seminar.models as m import seminar.models as m
from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Tema, Clanek, Osoba # 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, Tema, Clanek # 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, ProfileEditForm, PoMaturiteProfileEditForm
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
@ -41,9 +38,7 @@ import csv
import logging import logging
import time import time
from seminar.utils import aktivniResitele, problemy_rocniku, cisla_rocniku, hlavni_problemy_f from seminar.utils import aktivniResitele
from various.autentizace.views import LoginView
from various.autentizace.utils import posli_reset_hesla
# ze starého modelu # ze starého modelu
#def verejna_temata(rocnik): #def verejna_temata(rocnik):
@ -825,51 +820,6 @@ def oldObalkovaniView(request, rocnik, cislo):
{'cislo': cislo, 'problemy': problemy, 'reseni': reseni} {'cislo': cislo, 'problemy': problemy, 'reseni': reseni}
) )
### Orgostránky
class OrgoRozcestnikView(TemplateView):
''' Zobrazí organizátorský rozcestník.'''
template_name = 'seminar/orgorozcestnik.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['posledni_soustredeni'] = Soustredeni.objects.order_by('-datum_konce').first()
nastaveni = Nastaveni.objects.first()
aktualni_rocnik = nastaveni.aktualni_rocnik
context['posledni_cislo_url'] = nastaveni.aktualni_cislo.verejne_url()
# TODO možná chceme odkazovat na právě rozpracované číslo, a ne to poslední vydané
# pokud nechceme haluzit kód (= poradi) dalšího čísla, bude asi potřeba jít
# přes treenody (a dát si přitom pozor na MezicisloNode)
neobodovana_reseni = s.Hodnoceni.objects.filter(body__isnull=True)
reseni_mimo_cislo = s.Hodnoceni.objects.filter(cislo_body__isnull=True)
context['pocet_neobodovanych_reseni'] = neobodovana_reseni.count()
context['pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.count()
u = self.request.user
os = s.Osoba.objects.get(user=u)
organizator = s.Organizator.objects.get(osoba=os)
context['muj_pocet_neobodovanych_reseni'] = neobodovana_reseni.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).distinct().count()
context['muj_pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).count()
#FIXME: přidat stav='STAV_ZADANY'
temata = s.Tema.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
rocnik=aktualni_rocnik).distinct()
ulohy = s.Uloha.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
cislo_zadani__rocnik=aktualni_rocnik).distinct()
clanky = s.Clanek.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
cislo__rocnik=aktualni_rocnik).distinct()
context['temata'] = temata
context['ulohy'] = ulohy
context['clanky'] = clanky
context['organizator'] = organizator
return context
#content_type = 'text/plain; charset=UTF8'
#XXX
### Tituly ### Tituly
@ -1014,15 +964,6 @@ def StavDatabazeView(request):
'jmena_zen': utils.histogram([r.osoba.jmeno for r in zeny]), 'jmena_zen': utils.histogram([r.osoba.jmeno for r in zeny]),
}) })
class ResitelView(LoginRequiredMixin,generic.DetailView):
model = Resitel
template_name = 'seminar/profil/resitel.html'
def get_object(self, queryset=None):
print(self.request.user)
return Resitel.objects.get(osoba__user=self.request.user)
### Formulare ### Formulare
# pro přidání políčka do formuláře je potřeba # pro přidání políčka do formuláře je potřeba
@ -1031,216 +972,6 @@ class ResitelView(LoginRequiredMixin,generic.DetailView):
# - přidat do forms # - přidat do forms
# - includovat do html # - includovat do html
def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data):
msg = "{}, form_hash:{}".format(msg,hash(frozenset(form_data.items)))
logger.warn(msg)
gdpr_logger.warn(msg+", form:{}".format(form_data))
from django.forms.models import model_to_dict
@sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola')
def resitelEditView(request):
err_logger = logging.getLogger('seminar.prihlaska.problem')
## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli
u = request.user
osoba_edit = Osoba.objects.get(user=u)
if hasattr(osoba_edit,'resitel'):
resitel_edit = osoba_edit.resitel
else:
resitel_edit = None
user_edit = osoba_edit.user
## Vytvoření slovníku, kterým předvyplním formulář
prefill_1=model_to_dict(user_edit)
if resitel_edit:
prefill_2=model_to_dict(resitel_edit)
prefill_1.update(prefill_2)
prefill_3=model_to_dict(osoba_edit)
prefill_1.update(prefill_3)
if 'datum_narozeni' in prefill_1:
prefill_1['datum_narozeni'] = str(prefill_1['datum_narozeni'])
if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year:
form = PoMaturiteProfileEditForm(initial=prefill_1)
else:
form = ProfileEditForm(initial=prefill_1)
## Změna údajů a jejich uložení
if request.method == 'POST':
POST = request.POST.copy()
POST["username"] = osoba_edit.user.username
if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year:
form = PoMaturiteProfileEditForm(POST)
else:
form = ProfileEditForm(POST)
form.username = user_edit.username
if form.is_valid():
## Změny v osobě
fcd = form.cleaned_data
form_hash = hash(frozenset(fcd.items()))
form_logger = logging.getLogger('seminar.prihlaska.form')
form_logger.info("EDIT:" + str(fcd) + str(form_hash)) # TODO možná logovat jinak
osoba_edit.jmeno = fcd['jmeno']
osoba_edit.prijmeni = fcd['prijmeni']
osoba_edit.pohlavi_muz = fcd['pohlavi_muz']
osoba_edit.email = fcd['email']
osoba_edit.telefon = fcd['telefon']
osoba_edit.ulice = fcd['ulice']
osoba_edit.mesto = fcd['mesto']
osoba_edit.psc = fcd['psc']
osoba_edit.datum_narozeni = fcd['datum_narozeni']
## Změny v osobě s podmínkami
if fcd.get('spam',False):
osoba_edit.datum_souhlasu_zasilani = date.today()
if fcd.get('stat','') in ('CZ','SK'):
osoba_edit.stat = fcd['stat']
else:
## Neznámá země
msg = "Unknown country {}".format(fcd['stat_text'])
if resitel_edit:
## Změny v řešiteli
resitel_edit.skola = fcd['skola']
resitel_edit.rok_maturity = fcd['rok_maturity']
resitel_edit.zasilat = fcd['zasilat']
resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
if fcd.get('skola'):
resitel_edit.skola = fcd['skola']
else:
# Unknown school - log it
msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
resitel_edit.save()
osoba_edit.save()
return formularOKView(request, text=f'Údaje byly úspěšně uloženy. <a href="{reverse("profil")}">Vrátit se zpět na profil.</a>')
return render(request, 'seminar/profil/edit.html', {'form': form})
@sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola')
def prihlaskaView(request):
generic_logger = logging.getLogger('seminar.prihlaska')
err_logger = logging.getLogger('seminar.prihlaska.problem')
form_logger = logging.getLogger('seminar.prihlaska.form')
if request.method == 'POST':
form = PrihlaskaForm(request.POST)
# TODO vyresit, co se bude v jakych situacich zobrazovat
if form.is_valid():
generic_logger.info("Form valid")
fcd = form.cleaned_data
form_hash = hash(frozenset(fcd.items()))
form_logger.info(str(fcd) + str(form_hash)) # TODO možná logovat jinak
with transaction.atomic():
u = User.objects.create_user(
username=fcd['username'],
email = fcd['email'])
u.save()
resitel_perm = Permission.objects.filter(codename__exact='resitel').first()
u.user_permissions.add(resitel_perm)
resitel_grp = Group.objects.filter(name__exact='resitel').first()
u.groups.add(resitel_grp)
o = Osoba(
jmeno = fcd['jmeno'],
prijmeni = fcd['prijmeni'],
pohlavi_muz = fcd['pohlavi_muz'],
email = fcd['email'],
telefon = fcd.get('telefon',''),
datum_narozeni = fcd.get('datum_narozeni',None),
datum_souhlasu_udaje = date.today(),
datum_registrace = date.today(),
ulice = fcd.get('ulice',''),
mesto = fcd.get('mesto',''),
psc = fcd.get('psc',''),
poznamka = str(fcd)
)
if fcd.get('spam',False):
o.datum_souhlasu_zasilani = date.today()
if fcd.get('stat','') in ('CZ','SK'):
o.stat = fcd['stat']
else:
# Unknown country - log it
msg = "Unknown country {}".format(fcd['stat_text'])
err_logger.warn(msg + str(form_hash))
# Dovolujeme doregistraci uživatele pro existující mail, takže naopak chceme doplnit/aktualizovat údaje do stávajícího objektu
try:
orig_osoba = m.Osoba.objects.get(email=fcd['email'])
orig_osoba.poznamka += '\nDOREGISTRACE K EXISTUJÍCÍMU E-MAILU, diff níže.'
except m.Osoba.DoesNotExist:
# Trik: Budeme aktualizovat údaje nové osoby, takže se asi nic nezmění, ale fungovat to bude.
orig_osoba = o
# Porovnání údajů
assert orig_osoba.user is None, "Právě-registrující-se osoba už má Uživatele!"
osoba_attrs = ['jmeno', 'prijmeni', 'pohlavi_muz', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'stat', 'datum_souhlasu_udaje', 'datum_souhlasu_zasilani', 'datum_registrace']
diffattrs = []
for attr in osoba_attrs:
new = getattr(o, attr)
old = getattr(orig_osoba, attr)
if new != old:
orig_osoba.poznamka += f'\nRozdíl v {attr}: Původní {old}, nový {new}'
diffattrs.append(f'Osoba.{attr}')
setattr(orig_osoba, attr, new)
# Datum registrace chceme původní / nižší:
orig_osoba.datum_registrace = min(orig_osoba.datum_registrace, o.datum_registrace)
# Od této chvíle dál je správná osoba ta "původní", novou podle formuláře si ale zachováme
o, o_form = orig_osoba, o
o.save()
o.user = u
o.save()
# Jednoduchá kvazi-kontrola duplicitních Osob
kolize = m.Osoba.objects.filter(jmeno=o.jmeno, prijmeni=o.prijmeni)
if kolize.count() > 1: # Jednu z nich jsme právě uložili
err_logger.warning(f'Zaregistrovala se osoba s kolizním jménem. ID osob: {[o.id for o in kolize]}')
r = Resitel(
rok_maturity = fcd['rok_maturity'],
zasilat = fcd['zasilat'],
zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
)
if fcd.get('skola'):
r.skola = fcd['skola']
else:
# Unknown school - log it
msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
err_logger.warn(msg + str(form_hash))
# Porovnání údajů u řešitele
try:
orig_resitel = o.resitel
orig_resitel.poznamka += '\nDOREGISTRACE ŘEŠITELE, diff:'
except m.Resitel.DoesNotExist:
# Stejný trik:
orig_resitel = r
resitel_attrs = ['skola', 'rok_maturity', 'zasilat', 'zasilat_cislo_emailem']
for attr in resitel_attrs:
new = getattr(r, attr)
old = getattr(orig_resitel, attr)
if new != old:
orig_resitel.poznamka += f'\nRozdíl v {attr}: Původní {old}, nový {new}'
diffattrs.append(f'Resitel.{attr}')
setattr(orig_resitel, attr, new)
r, r_form = orig_resitel, r
r.osoba = o # Tohle by mělo být bezpečné…
r.save()
if diffattrs: err_logger.warning(f'Different fields when matching Řešitel id {r.id} or Osoba id {o.id}: {diffattrs}')
posli_reset_hesla(u, request)
return formularOKView(request, text='Na tvůj e-mail jsme právě poslali odkaz pro nastavení hesla.')
# if a GET (or any other method) we'll create a blank form
else:
form = PrihlaskaForm()
return render(request, 'seminar/profil/prihlaska.html', {'form': form})
class VueTestView(generic.TemplateView): class VueTestView(generic.TemplateView):
template_name = 'seminar/vuetest.html' template_name = 'seminar/vuetest.html'
@ -1268,17 +999,6 @@ class NahrajObrazekKTreeNoduView(LoginRequiredMixin, CreateView):
return JsonResponse({"url":self.object.na_web.url}) return JsonResponse({"url":self.object.na_web.url})
# Jen hloupé rozhazovátko
def profilView(request):
user = request.user
if user.has_perm('auth.org'):
return OrgoRozcestnikView.as_view()(request)
if user.has_perm('auth.resitel'):
return ResitelView.as_view()(request)
else:
return LoginView.as_view()(request)
# Interní, nemá se nikdy objevit v urls (jinak to účastníci vytrolí) # Interní, nemá se nikdy objevit v urls (jinak to účastníci vytrolí)
def formularOKView(request, text=''): def formularOKView(request, text=''):
template_name = 'seminar/formular_ok.html' template_name = 'seminar/formular_ok.html'

Loading…
Cancel
Save