Compare commits

..

No commits in common. "4d5800f3b8a363a2b29a9777e15a1ecbde3dc1b7" and "9460c484f72c98648e4a0f623c34a5fa74965c8a" have entirely different histories.

41 changed files with 940 additions and 1405 deletions

View file

@ -1,6 +1,6 @@
from django.contrib import admin
from reversion.admin import VersionAdmin
from korektury.models import KorekturovanePDF, Oprava, KorekturaTag
from korektury.models import KorekturovanePDF
from django.core.mail import EmailMessage
from django.urls import reverse
@ -62,11 +62,3 @@ Korekturovátko
).send()
admin.site.register(KorekturovanePDF, KorekturovanePDFAdmin)
class OpravaAdmin(admin.ModelAdmin):
model = Oprava
filter_horizontal = ("informovani_orgove", "tagy",)
admin.site.register(Oprava, OpravaAdmin)
admin.site.register(KorekturaTag)

View file

@ -1,7 +0,0 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'korektury.api'
label = 'korektury_api' # Protože jedno api už máme.

View file

@ -1,11 +0,0 @@
from django.urls import path
from personalni.utils import org_required
from . import views
urlpatterns = [
path('<int:pdf_id>/stav', org_required(views.korektury_stav_view), name='korektury_api_pdf_stav'),
path('oprava/stav', org_required(views.oprava_stav_view), name='korektury_api_oprava_stav'),
path('<int:pdf_id>/opravy_a_komentare', org_required(views.opravy_a_komentare_view), name='korektury_api_opravy_a_komentare'),
path('oprava/smaz', org_required(views.oprava_smaz_view), name='korektury_api_oprava_smaz'),
path('komentar/smaz', org_required(views.komentar_smaz_view), name='korektury_api_komentar_smaz'),
]

View file

@ -1,129 +0,0 @@
from http import HTTPStatus
from django.http import JsonResponse, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.html import linebreaks
from rest_framework import serializers
from korektury.utils import send_email_notification_komentar
from korektury.models import Oprava, KorekturovanePDF, Komentar, KorekturaTag
from personalni.models import Organizator
def korektury_stav_view(request, pdf_id: int, **kwargs):
q = request.POST
pdf = get_object_or_404(KorekturovanePDF, id=pdf_id)
status = q.get('state')
if status is not None:
assert status in KorekturovanePDF.STATUS.values
pdf.status = status
pdf.save()
return JsonResponse({'status': pdf.status})
def oprava_stav_view(request, **kwargs):
q = request.POST
op_id_str = q.get('id')
assert op_id_str is not None
op_id = int(op_id_str)
op = get_object_or_404(Oprava, id=op_id)
status = q.get('action')
if status is not None:
assert status in Oprava.STATUS.values
op.status = status
op.save()
return JsonResponse({'status': op.status})
def oprava_smaz_view(request, **kwargs):
q = request.POST
op_id_str = q.get('oprava_id')
assert op_id_str is not None
op_id = int(op_id_str)
oprava = get_object_or_404(Oprava, id=op_id)
oprava.delete()
return HttpResponse(status=HTTPStatus.NO_CONTENT)
def komentar_smaz_view(request, **kwargs):
q = request.POST
kom_id_str = q.get('komentar_id')
assert kom_id_str is not None
kom_id = int(kom_id_str)
komentar = get_object_or_404(Komentar, id=kom_id)
komentar.delete()
return HttpResponse(status=HTTPStatus.NO_CONTENT)
class KomentarSerializer(serializers.ModelSerializer):
class Meta:
model = Komentar
fields = '__all__'
def to_representation(self, instance):
ret = super().to_representation(instance)
ret["autor"] = str(instance.autor)
ret["text"] = linebreaks(ret["text"], autoescape=True) # Autora není třeba escapovat, ten se vkládá jako text.
return ret
class KorekturaTagSerializer(serializers.ModelSerializer):
class Meta:
model = KorekturaTag
fields = '__all__'
class OpravaSerializer(serializers.ModelSerializer):
class Meta:
model = Oprava
fields = '__all__'
def to_representation(self, instance):
ret = super().to_representation(instance)
ret["komentare"] = [KomentarSerializer(komentar).data for komentar in instance.komentar_set.all()]
ret["tagy"] = [KorekturaTagSerializer(tag).data for tag in instance.tagy.all()]
return ret
# komentar_set = serializers.ListField(child=KomentarSerializer())
def opravy_a_komentare_view(request, pdf_id: int, **kwargs):
if request.method == 'POST':
q = request.POST
x = int(q.get('x'))
y = int(q.get('y'))
img_id = int(q.get('img_id'))
oprava_id = int(q.get('oprava_id'))
komentar_id = int(q.get('komentar_id'))
text = q.get('text')
# prirazeni autora podle prihlaseni
autor_user = request.user
# pokud existuje ucet (user), ale neni to organizator = 403
autor = Organizator.objects.filter(osoba__user=autor_user).first()
if komentar_id != -1:
komentar = get_object_or_404(Komentar, id=komentar_id)
komentar.text = text
komentar.autor = autor
komentar.save()
else:
if oprava_id != -1:
oprava = get_object_or_404(Oprava, id=oprava_id)
else:
pdf = get_object_or_404(KorekturovanePDF, id=pdf_id)
oprava = Oprava.objects.create(
pdf=pdf,
strana=img_id,
x=x,
y=y,
)
tagy_raw = q.get('tagy')
if tagy_raw != "":
tagy = list(map(int, tagy_raw.split(",")))
oprava.tagy.add(*KorekturaTag.objects.filter(id__in=tagy))
Komentar.objects.create(oprava=oprava, autor=autor, text=text)
send_email_notification_komentar(oprava, autor, request)
opravy = Oprava.objects.filter(pdf=pdf_id).all()
# Serializovat list je prý security vulnerability, tedy je přidán slovník pro bezpečnost
return JsonResponse({"context": [OpravaSerializer(oprava).data for oprava in opravy]})

14
korektury/forms.py Normal file
View file

@ -0,0 +1,14 @@
from django import forms
class OpravaForm(forms.Form):
""" formulář k přidání opravy (:class:`korektury.models.Oprava`) """
text = forms.CharField(max_length=256)
autor = forms.CharField(max_length=20)
x = forms.IntegerField()
y = forms.IntegerField()
scroll = forms.CharField(max_length=256)
pdf = forms.CharField(max_length=256)
img_id = forms.CharField(max_length=256)
id = forms.CharField(max_length=256)
action = forms.CharField(max_length=256)

View file

@ -1,45 +0,0 @@
# Generated by Django 4.2.16 on 2024-12-12 10:25
from django.db import migrations
import datetime
from django.utils import timezone
def oprava2komentar(apps, schema_editor):
Oprava = apps.get_model('korektury', 'Oprava')
Komentar = apps.get_model('korektury', 'Komentar')
for o in Oprava.objects.all():
Komentar.objects.create(oprava=o, text=o.text, autor=o.autor, cas=timezone.make_aware(datetime.datetime.fromtimestamp(0)))
def komentar2oprava(apps, schema_editor):
Oprava = apps.get_model('korektury', 'Oprava')
Komentar = apps.get_model('korektury', 'Komentar')
for o in Oprava.objects.all():
k = Komentar.objects.filter(oprava=o).first()
o.text = k.text
o.autor = k.autor
o.save()
k.delete()
class Migration(migrations.Migration):
dependencies = [
('korektury', '0024_vic_orgu_k_pdf'),
]
operations = [
migrations.RunPython(oprava2komentar, komentar2oprava),
migrations.RemoveField(
model_name='oprava',
name='autor',
),
migrations.RemoveField(
model_name='oprava',
name='text',
),
]

View file

@ -1,29 +0,0 @@
# Generated by Django 4.2.16 on 2025-02-11 14:28
from django.db import migrations, models
def pridani_orgu(apps, _schema_editor):
Komentar = apps.get_model('korektury','Komentar')
for komentar in Komentar.objects.all():
org = komentar.autor
if org is not None:
# Tohle jde asi udělat lépe než .all(…). Ale nejhorší na tom je, že .add(…) funguje jinak tady v migracích.
if org not in komentar.oprava.informovani_orgove.all():
komentar.oprava.informovani_orgove.add(org)
class Migration(migrations.Migration):
dependencies = [
('personalni', '0019_rename_upozorneni_resitel_upozornovat_na_opravy_reseni'),
('korektury', '0025_remove_oprava_autor_remove_oprava_text'),
]
operations = [
migrations.AddField(
model_name='oprava',
name='informovani_orgove',
field=models.ManyToManyField(blank=True, default=None, help_text='Orgové informovaní při přidání komentáře ke korektuře', related_name='informovan_o_opravach', to='personalni.organizator', verbose_name='Informovaní organizátoři'),
),
migrations.RunPython(pridani_orgu, migrations.RunPython.noop),
]

View file

@ -1,27 +0,0 @@
# Generated by Django 4.2.16 on 2025-02-11 16:07
import colorfield.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('korektury', '0026_oprava_informovani_orgove'),
]
operations = [
migrations.CreateModel(
name='KorekturaTag',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nazev', models.CharField(help_text='Název daného tagu, <20 znaků', max_length=20, verbose_name='název tagu')),
('barva', colorfield.fields.ColorField(default='#FFFFFF', image_field=None, max_length=25, samples=None, verbose_name='barva daného tagu')),
],
),
migrations.AddField(
model_name='oprava',
name='tagy',
field=models.ManyToManyField(blank=True, default=None, to='korektury.korekturatag'),
),
]

View file

@ -1,7 +1,4 @@
import os
from colorfield.fields import ColorField
from django.db import models
from django.urls import reverse
from django.utils import timezone
@ -55,13 +52,16 @@ class KorekturovanePDF(models.Model):
stran = models.IntegerField(u'počet stran', help_text='Počet stran PDF',
default=0)
class STATUS(models.TextChoices):
PRIDAVANI = 'pridavani', 'Přidávání korektur'
ZANASENI = 'zanaseni', 'Korektury jsou zanášeny'
ZASTARALE = 'zastarale', 'Stará verze, nekorigovat'
status = models.CharField(u'stav PDF',max_length=16, choices=STATUS.choices, blank=False, default = STATUS.PRIDAVANI)
STATUS_PRIDAVANI = 'pridavani'
STATUS_ZANASENI = 'zanaseni'
STATUS_ZASTARALE = 'zastarale'
STATUS_CHOICES = (
(STATUS_PRIDAVANI, u'Přidávání korektur'),
(STATUS_ZANASENI, u'Korektury jsou zanášeny'),
(STATUS_ZASTARALE, u'Stará verze, nekorigovat'),
)
status = models.CharField(u'stav PDF',max_length=16, choices=STATUS_CHOICES, blank=False,
default = STATUS_PRIDAVANI)
poslat_mail = models.BooleanField('Poslat mail o novém PDF', default=True,
help_text='Určuje, zda se má o nově nahraném PDF poslat e-mail do mam-org. Při upravování existujícího souboru už nemá žádný vliv.',
@ -134,14 +134,6 @@ class KorekturovanePDF(models.Model):
return reverse('korektury', kwargs={'pdf': self.id})
class KorekturaTag(models.Model):
nazev = models.CharField("název tagu", blank = False, max_length=20, help_text="Název daného tagu, <20 znaků")
barva = ColorField("barva daného tagu", default="#FFFFFF")
def __str__(self):
return self.nazev
@reversion.register(ignore_duplicates=True)
class Oprava(models.Model):
class Meta:
@ -160,22 +152,32 @@ class Oprava(models.Model):
x = models.IntegerField(u'x-ová souřadnice bugu')
y = models.IntegerField(u'y-ová souřadnice bugu')
class STATUS(models.TextChoices):
K_OPRAVE = 'k_oprave', 'K opravě'
OPRAVENO = 'opraveno', 'Opraveno'
NENI_CHYBA = 'neni_chyba', 'Není chyba'
K_ZANESENI = 'k_zaneseni', 'K zanesení do TeXu'
STATUS_K_OPRAVE = 'k_oprave'
STATUS_OPRAVENO = 'opraveno'
STATUS_NENI_CHYBA = 'neni_chyba'
STATUS_K_ZANESENI = 'k_zaneseni'
STATUS_CHOICES = (
(STATUS_K_OPRAVE, u'K opravě'),
(STATUS_OPRAVENO, u'Opraveno'),
(STATUS_NENI_CHYBA, u'Není chyba'),
(STATUS_K_ZANESENI, u'K zanesení do TeXu'),
)
status = models.CharField(u'stav opravy',max_length=16, choices=STATUS_CHOICES, blank=False,
default = STATUS_K_OPRAVE)
status = models.CharField(u'stav opravy',max_length=16, choices=STATUS.choices, blank=False, default = STATUS.K_OPRAVE)
autor = models.ForeignKey(Organizator, blank = True,
help_text='Autor opravy',
null = True, on_delete=models.SET_NULL)
text = models.TextField(u'text opravy',blank = True, help_text='Text opravy')
informovani_orgove = models.ManyToManyField(
Organizator, blank=True, default=None,
verbose_name='Informovaní organizátoři',
help_text="Orgové informovaní při přidání komentáře ke korektuře",
related_name='informovan_o_opravach',
)
# def __init__(self,dictionary):
# for k,v in dictionary.items():
# setattr(self,k,v)
def __str__(self):
return '{} od {}: {}'.format(self.status,self.autor,self.text)
tagy = models.ManyToManyField(KorekturaTag, blank=True, default=None,)
@reversion.register(ignore_duplicates=True)
@ -201,7 +203,5 @@ class Komentar(models.Model):
def __str__(self):
return '{} od {}: {}'.format(self.cas,self.autor,self.text)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self.autor is not None:
self.oprava.informovani_orgove.add(self.autor)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 270 B

View file

@ -1,227 +1,136 @@
.textzanaseni { display:none; }
.textzastarale { display:none; }
#prekomentar, #preoprava, #prepointer { display: none; }
body {
&[data-status="pridavani"] {
background: #f3f3f3;
}
&[data-status="zanaseni"] {
background: yellow;
.textzanaseni { display: unset; }
}
&[data-status="zastarale"] {
background: red;
.textzastarale { display: unset; }
}
body,
.adding{
background: #f3f3f3;
color: black;
}
.comitting
{
background: yellow;
}
.deprecated {
background: red;
}
img{background:white;}
/* Barvy korektur */
[data-opravastatus="k_oprave"] {
.k_oprave {
--rgb: 255, 0, 0;
[value="k_oprave"] { display: none }
.komentovat_disabled { display: none }
}
[data-opravastatus="opraveno"] {
.opraveno {
--rgb: 0, 0, 255;
[value="opraveno"] { display: none }
.komentovat { display: none }
}
[data-opravastatus="neni_chyba"] {
.neni_chyba {
--rgb: 128, 128, 128;
[value="neni_chyba"] { display: none }
.komentovat { display: none }
}
[data-opravastatus="k_zaneseni"] {
.k_zaneseni {
--rgb: 0, 255, 0;
[value="k_zaneseni"] { display: none }
.komentovat { display: none }
}
/* Skrývání korektur */
[data-opravazobrazit="false"] {
.corr-body { display: none; }
.corr-buttons { display: none; }
.toggle-button { transform: rotate(180deg); }
}
/* Tlačítko na aktualizaci */
#korektury-aktualizace {
position: absolute;
right: 10px;
top: 10px;
border-radius: 10px;
/* copy-paste .button */
margin: 10px 0 10px 0;
padding: 4px 0; /*vertikální centování textu*/
text-align: center;
background-color: #e84e10;
color: #fffbf6;
font-size: 150%;
font-weight: bold;
font-variant: small-caps;
filter: drop-shadow(0px 5px 5px rgba(0, 0, 0, 0.4));
&:hover {
background-color: #df490e;
}
}
/* Čára od textu k místu korektury */
.pointer-hi,
.pointer{
position:absolute;
border-bottom-left-radius: 10px;
border-left: 1px solid rgb(var(--rgb),var(--alpha));
border-bottom: 1px solid rgb(var(--rgb),var(--alpha));
pointer-events: none;
--alpha: 0.35;
/* Zvýraznění čáry při najetí na korekturu */
&[data-highlight="true"] {
border-width: 3px;
--alpha: 1;
}
/*border-bottom-left-radius: 10px; */
border-left: 2px solid yellow;
border-bottom: 2px solid yellow;
border-color: rgb(var(--rgb),var(--alpha));
}
/* Korektura samotná */
.oprava {
.pointer {
border-width: 1px;
--alpha: 0.35;
}
.pointer-hi {
border-width: 3px;
--alpha: 1;
}
.box:hover{
border-width:3px;
margin: 0px;
}
.box {
margin: 1px;
background-color: white;
width: 300px;
width:300px;
/*position:absolute;*/
padding: 3px;
border: 2px solid rgb(var(--rgb));
border: 2px solid black;
border-radius: 10px;
position: absolute;
&:hover {
border-width:3px;
margin: 0;
}
button, img {
border: 1px solid white;
background-color:transparent;
margin:0;
padding: 1px;
&:hover {
border: 1px solid black;
}
}
button img { pointer-events: none; }
.corr-header {
overflow: auto;
}
.author {
font-weight: bold;
float: left;
margin-top: 3px;
}
.float-right{
float:right;
}
border-color: rgb(var(--rgb));
}
form {
display:inline;
}
/* Zobrazované PDF */
.imgdiv {
position:relative;
left:0;
top:0;
.float-right{
float:right;
}
/* Přidávání korektury / úprava komentáře */
.imgdiv {
position:relative;
left:0px;
top:0px;
}
#commform-div {
display: none;
position: absolute;
background-color: white;
border: 1px solid;
padding: 3px;
/*
width: 310;
height: 220;
*/
z-index: 10;
border: 4px solid red;
border-radius: 10px;
background-color: white;
opacity: 80%;
}
.korektury-tag {
border-radius: 5px;
margin: 2px;
padding: 2px;
&[data-selected="false"] { opacity: 0.7; }
}
/* Šipky na posouvání korektur */
#korektury-sipky {
position: fixed;
bottom: 5px;
left: 5px;
button, img {
border: 1px solid white;
background-color:transparent;
margin:0;
padding: 1px;
border-radius: 5px;
&:hover {
border: 1px solid black;
}
}
button img { pointer-events: none; }
#predchozi-korektura, #dalsi-korektura {
background-color: #EEEEEE;
}
#predchozi-korektura-k-oprave, #dalsi-korektura-k-oprave {
background-color: #FF0000;
}
#predchozi-korektura-k-zaneseni, #dalsi-korektura-k-zaneseni {
background-color: #00FF00;
}
.close-button{
background-color: yellow;
}
/**** ROZLIŠENÍ MEZI LOKÁLNÍM, TESTOVACÍM A PRODUKČNÍM WEBEM ****/
body.localweb, body.testweb, body.suprodweb {
&:before, &:after {
content: "";
position: fixed;
width: 20px;
height: 100%;
top: 0;
z-index: -1000;
}
&:before { left: 0; }
&:after { right: 0; }
.box button,
.box img,
.box-done button,
.box-done img,
.box-ready button,
.box-ready img,
.box-wontfix button,
.box-wontfix img{
border: 1px solid white;
background-color:transparent;
margin:0;
padding: 1px;
}
.box button:hover,
.box img:hover,
.box-done img:hover,
.box-done button:hover,
.box-ready img:hover,
.box-ready button:hover,
.box-wontfix img:hover,
.box-wontfix button:hover{
border: 1px solid black;
}
.comment hr {
height: 0px;
}
.corr-header {
overflow: auto;
}
.author {
font-weight: bold;
float: left;
margin-top: 3px;
}
body.localweb { &:before, &:after { background: greenyellow; } }
body.testweb { &:before, &:after { background: darkorange; } }
body.suprodweb { &:before, &:after { background: red; } }
/****************************************************************/

View file

@ -1,46 +1,283 @@
const W_SKIP = 10;
const H_SKIP = 5;
const POINTER_MIN_H = 30;
function place_comments_one_div(img_id, comments)
{
const img = document.getElementById("img-"+img_id);
if( img == null ) return;
const comments_sorted = comments.sort((a, b) => a.y - b.y);
const par = img.parentNode;
const w = img.clientWidth;
let bott_max = 0;
for (const oprava of comments_sorted) {
const x = oprava.x;
const y = oprava.y;
const htmlElement = oprava.htmlElement;
const pointer = oprava.pointer;
par.appendChild(pointer);
par.appendChild(htmlElement);
const delta_y = (y > bott_max) ? 0: bott_max - y + H_SKIP;
pointer.style.left = x;
pointer.style.top = y;
pointer.style.width = w - x + W_SKIP;
pointer.style.height = POINTER_MIN_H + delta_y;
htmlElement.style.left = w + W_SKIP;
htmlElement.style.top = y + delta_y;
bott_max = Math.max(bott_max, htmlElement.offsetTop + htmlElement.offsetHeight + H_SKIP); // FIXME nemám páru, proč +H_SKIP funguje, ale opravuje to bug, že nově vytvořené korektury za sebou neměly mezeru
var img = document.getElementById(img_id);
if( img == null ) {
return;
}
var par = img.parentNode;
var w = img.clientWidth;
var h = img.clientHeight;
var w_skip = 10;
var h_skip = 5;
var pointer_min_h = 30;
if (par.offsetHeight < bott_max) par.style.height = bott_max;
var bott_max = 0;
var comments_sorted = comments.sort(function (a,b) {
return a[2] - b[2];
//pokus o hezci kladeni poiteru, ale nic moc
if( a[3] < b[3] ) {
return (a[2] + pointer_min_h)- b[2];
} else {
return (a[2] - pointer_min_h)- b[2];
}
});
//console.log("w:" + w);
for (c in comments_sorted) {
var id = comments_sorted[c][0];
var x = comments_sorted[c][1];
var y = comments_sorted[c][2];
var el = document.getElementById(id);
var elp = document.getElementById(id + "-pointer");
if( el == null || elp == null ) {
continue;
}
par.appendChild(elp);
par.appendChild(el);
var delta_y = (y > bott_max) ? 0: bott_max - y + h_skip;
elp.style.left = x;
elp.style.top = y ;
elp.style.width = w - x + w_skip;
elp.style.height = pointer_min_h + delta_y;
elp.img_id = img_id;
el.img_id = img_id;
el.style.position = 'absolute';
el.style.left = w + w_skip;
el.style.top = y + delta_y;
var bott = el.offsetTop + el.offsetHeight;
bott_max = ( bott_max > bott ) ? bott_max : bott;
//console.log( "par.w:" + par.style.width);
}
if( par.offsetHeight < bott_max ) {
//par.style.height = bott_max;
//alert("preteklo to:"+ par.offsetHeight +",mx:" + bott_max );
par.style.height = bott_max;
}
}
function place_comments() {
for (let [img_id, opravy] of Object.entries(comments)) {
place_comments_one_div(img_id, opravy)
for (var i=0; i < comments.length-1; i++) {
place_comments_one_div(comments[i][0], comments[i][1])
}
}
// ctrl-enter submits form
function textarea_onkey(ev)
{
//console.log("ev:" + ev.keyCode + "," + ev.ctrlKey);
if( (ev.keyCode == 13 || ev.keyCode == 10 ) && ev.ctrlKey ) {
var form = document.getElementById('commform');
if( form ) {
save_scroll(form);
//form.action ='';
form.submit();
}
return true;
}
return false;
}
//hide comment form
function close_commform() {
var formdiv = document.getElementById('commform-div');
if( formdiv == null ) {
alert("form null");
return true;
}
formdiv.style.display = 'none';
return false;
}
// show comment form, when clicked to image
function img_click(element, ev) {
var body_class = document.body.className;
switch(body_class){
case "comitting":
if (!confirm("Právě jsou zanášeny korektury, opravdu chcete přidat novou?"))
return;
break;
case "deprecated":
if (!confirm("Toto PDF je již zastaralé, opravdu chcete vytvořit korekturu?"))
return;
break;
}
var dx, dy;
var par = element.parentNode;
if( ev.pageX != null ) {
dx = ev.pageX - par.offsetLeft;
dy = ev.pageY - par.offsetTop;
} else { //IE
dx = ev.offsetX;
dy = ev.offsetY;
}
var img_id = element.id;
if( element.img_id != null ) {
// click was to '-pointer'
img_id = element.img_id;
}
return show_form(img_id, dx, dy, '', '', '', '');
}
// hide or show text of correction
function toggle_visibility(oid){
var buttondiv = document.getElementById(oid+'-buttons')
var text = document.getElementById(oid+'-body');
if (text.style.display == 'none'){
text.style.display = 'block';
buttondiv.style.display = 'inline-block';
}else {
text.style.display = 'none';
buttondiv.style.display = 'none';
}
for (var i=0;i<comments.length-1;i++){
place_comments_one_div(comments[i][0], comments[i][1])
}
}
// show comment form, when 'edit' or 'comment' button pressed
function box_edit(oid, action)
{
var divpointer = document.getElementById(oid + '-pointer');
var text;
if (action == 'update') {
var text_el = document.getElementById(oid + '-text');
text = text_el.textContent; // FIXME původně tu bylo innerHTML.unescapeHTML()
} else {
text = '';
}
var dx = parseInt(divpointer.style.left);
var dy = parseInt(divpointer.style.top);
var divbox = document.getElementById(oid);
//alert('not yet 2:' + text + text_el); // + divpointer.style.top "x" + divpo );
id = oid.substring(2);
return show_form(divbox.img_id, dx, dy, id, text, action);
}
// show comment form when 'update-comment' button pressed
function update_comment(oid,ktid)
{
var divpointer = document.getElementById(oid + '-pointer');
var dx = parseInt(divpointer.style.left);
var dy = parseInt(divpointer.style.top);
var divbox = document.getElementById(oid);
var text = document.getElementById(ktid).textContent; // FIXME původně tu bylo innerHTML.unescapeHTML()
return show_form(divbox.img_id, dx, dy, ktid.substring(2), text, 'update-comment');
}
//fill up comment form and show him
function show_form(img_id, dx, dy, id, text, action) {
var form = document.getElementById('commform');
var formdiv = document.getElementById('commform-div');
var textarea = document.getElementById('commform-text');
var inputX = document.getElementById('commform-x');
var inputY = document.getElementById('commform-y');
var inputImgId = document.getElementById('commform-img-id');
var inputId = document.getElementById('commform-id');
var inputAction = document.getElementById('commform-action');
var img = document.getElementById(img_id);
if( formdiv == null || textarea == null ) {
alert("form null");
return 1;
}
//form.action = "#" + img_id;
// set hidden values
inputX.value = dx;
inputY.value = dy;
inputImgId.value = img_id;
inputId.value = id;
inputAction.value = action;
textarea.value = text;
//textarea.value = "dxy:"+ dx + "x" + dy + "\n" + 'id:' + img_id;
// show form
formdiv.style.display = 'block';
formdiv.style.left = dx;
formdiv.style.top = dy;
img.parentNode.appendChild(formdiv);
textarea.focus();
return true;
}
function box_onmouseover(box)
{
var id = box.id;
var pointer = document.getElementById(box.id + '-pointer');
pointer.classList.remove('pointer');
pointer.classList.add('pointer-hi');
}
function box_onmouseout(box)
{
var id = box.id;
var pointer = document.getElementById(box.id + '-pointer');
pointer.classList.remove('pointer-hi');
pointer.classList.add('pointer');
}
function save_scroll(form)
{
//alert('save_scroll:' + document.body.scrollTop);
form.scroll.value = document.body.scrollTop;
//alert('save_scroll:' + form.scroll.value);
return true;
}
function toggle_corrections(aclass)
{
var stylesheets = document.styleSheets;
var ssheet = null;
for (var i=0;i<stylesheets.length; i++){
if (stylesheets[i].title === "opraf-css"){
ssheet = stylesheets[i];
break;
}
}
if (! ssheet){
return;
}
for (var i=0;i<ssheet.cssRules.length;i++){
var rule = ssheet.cssRules[i];
if (rule.selectorText === '.'+aclass){
if (rule.style.display === ""){
rule.style.display = "none";
} else {
rule.style.display = "";
}
}
}
place_comments();
}
String.prototype.unescapeHTML = function () {
return(
this.replace(/&amp;/g,'&').
replace(/&gt;/g,'>').
replace(/&lt;/g,'<').
replace(/&quot;/g,'"')
);
};

View file

@ -1,67 +0,0 @@
{% load static %}
<div id="korektury-sipky">
<button type='button' id="predchozi-korektura" title='Předchozí korektura'>
<img class='toggle-button' src='{% static "korektury/imgs/hide.png" %}' alt='⬆'/>
</button>
<button type='button' id="predchozi-korektura-k-oprave" title='Předchozí korektura k opravě'>
<img class='toggle-button' src='{% static "korektury/imgs/hide.png" %}' alt='⬆'/>
</button>
<button type='button' id="predchozi-korektura-k-zaneseni" title='Předchozí korektura k zaneseni'>
<img class='toggle-button' src='{% static "korektury/imgs/hide.png" %}' alt='⬆'/>
</button>
<br>
<button type='button' id="dalsi-korektura" title='Další korektura'>
<img class='toggle-button' src='{% static "korektury/imgs/hide.png" %}' alt='⬇' style="transform: rotate(180deg);"/>
</button>
<button type='button' id="dalsi-korektura-k-oprave" title='Další korektura k opravě'>
<img class='toggle-button' src='{% static "korektury/imgs/hide.png" %}' alt='⬇' style="transform: rotate(180deg);"/>
</button>
<button type='button' id="dalsi-korektura-k-zaneseni" title='Další korektura k zaneseni'>
<img class='toggle-button' src='{% static "korektury/imgs/hide.png" %}' alt='⬇' style="transform: rotate(180deg);"/>
</button>
</div>
<script>
const predchozi_k = document.getElementById('predchozi-korektura');
const dalsi_k = document.getElementById('dalsi-korektura');
const predchozi_k_o = document.getElementById('predchozi-korektura-k-oprave');
const dalsi_k_o = document.getElementById('dalsi-korektura-k-oprave');
const predchozi_k_z = document.getElementById('predchozi-korektura-k-zaneseni');
const dalsi_k_z = document.getElementById('dalsi-korektura-k-zaneseni');
function dalsi_nebo_predchozi_korektura(dalsi=true, stav=null) {
let predchozi = null;
for (let [_, opravy] of Object.entries(comments)) {
for (const oprava of opravy) {
if (stav == null || oprava.status === stav) {
const y = oprava.htmlElement.getBoundingClientRect().y;
if (y >= -1) {
if (dalsi) {
if (y > 1) {
oprava.htmlElement.scrollIntoView();
return;
}
} else {
if (predchozi !== null) predchozi.htmlElement.scrollIntoView(); else alert("Výše už není žádná taková korektura.");
return;
}
}
predchozi = oprava;
}
}
}
if (!dalsi && predchozi !== null) {
predchozi.htmlElement.scrollIntoView();
return;
}
alert("Žádná další korektura.");
}
predchozi_k.addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(false) });
dalsi_k.addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(true) });
predchozi_k_o.addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(false, "k_oprave") });
dalsi_k_o.addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(true, "k_oprave") });
predchozi_k_z.addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(false, "k_zaneseni") });
dalsi_k_z.addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(true, "k_zaneseni") });
</script>

View file

@ -1,97 +0,0 @@
<div id="commform-div" style="display: none">
<input size="24" name="au" value="{{user.first_name}} {{user.last_name}}" readonly/>
<button type="button" id="commform-submit">Oprav!</button>
<button type="button" id="commform-close">Zavřít</button>
<br/>
<textarea id="commform-text" cols=40 rows=10 name="txt"></textarea>
<br/>
<span id="commform-tagy">
{% for tag in tagy %}
<button type="button" class="korektury-tag" value="{{tag.id}}" data-selected="false" style="background: {{ tag.barva }};">{{tag.nazev}}</button>
{% endfor %}
</span>
</div>
<script>
class _CommForm {
constructor() {
this.div = document.getElementById('commform-div');
this.text = document.getElementById('commform-text');
this.submit_button = document.getElementById('commform-submit');
const close_button = document.getElementById('commform-close');
this.tagy = document.getElementById('commform-tagy');
// ctrl-enter submits form
this.text.addEventListener("keydown", ev => {
if (ev.code === "Enter" && ev.ctrlKey) this.submit();
});
close_button.addEventListener("click", _ => { this.close(); });
this.submit_button.addEventListener("click", _ => { this.submit(); });
for (const tag of this.tagy.getElementsByTagName("button")) tag.addEventListener("click", event => { this.toggle_tag(event); });
}
toggle_tag(event) {
const button = event.target;
button.dataset.selected = String(button.dataset.selected === "false");
}
// schová commform
close() { this.div.style.display = 'none'; }
// zobrazí commform (bez vyplňování)
_show(img_id, x, y) {
this.submit_button.disabled = false;
this.div.style.display = 'block';
this.div.style.left = x;
this.div.style.top = y;
const img = document.getElementById("img-" + img_id);
img.parentNode.appendChild(commform.div);
this.text.focus();
}
// fill up comment form and show him
show(img_id, x, y, text, oprava_id=-1, komentar_id=-1) {
if (this.div.style.display !== 'none' && this.text.value !== "" && !confirm("Zavřít předchozí okénko přidávání korektury / editace komentáře?")) return;
// set hidden values
this.x = x;
this.y = y;
this.imgID = img_id;
this.oprava_id = oprava_id;
this.komentar_id = komentar_id;
this.text.value = text;
// show form
if (oprava_id === -1 && komentar_id === -1) this.tagy.style.display = 'unset'; else this.tagy.style.display = 'none';
this._show(img_id, x, y);
}
submit() {
this.submit_button.disabled = true;
const data = new FormData(CSRF_FORM);
data.append('x', this.x);
data.append('y', this.y);
data.append('img_id', this.imgID);
data.append('oprava_id', this.oprava_id);
data.append('komentar_id', this.komentar_id);
if (this.oprava_id === -1 && this.komentar_id === -1) {
const tagy = [];
for (const tag of this.tagy.getElementsByTagName("button")) {
if (tag.dataset.selected !== "false") tagy.push(tag.value);
}
data.append('tagy', String(tagy));
}
data.append('text', this.text.value);
update_all({method: 'POST', body: data}, true, () => {this.close(); this.submit_button.disabled = false;});
}
}
const commform = new _CommForm();
</script>

View file

@ -1,106 +0,0 @@
{% load static %}
<div class='comment' id='prekomentar' {# id='k{{k.id}}' #}>
<div class='corr-header'>
<div class='author'>{# {{k.autor}} #}</div>
<div class='float-right'>
<button type='button' style='display: none' class='del-comment' title='Smaž komentář'>
<img src='{% static "korektury/imgs/delete.png" %}' alt='del'/>
</button>
<button type='button' class='update-comment' title='Uprav komentář'>
<img src='{% static "korektury/imgs/edit.png"%}' alt='edit'/>
</button>
</div>
</div>
<div class='komtext'>{# {{k.text|linebreaks}} #}</div>
<hr>
</div>
<script>
const prekomentar = document.getElementById('prekomentar');
const komentare = {};
class Komentar {
static update_or_create(komentar_data, oprava) {
const id = komentar_data['id'];
if (id in komentare) komentare[id].update(komentar_data);
else new Komentar(komentar_data, oprava);
}
#autor; #text;
htmlElement;
id; oprava; {# komentar_data; #}
autor;
/**
*
* @param komentar_data
* @param {Oprava} oprava
*/
constructor(komentar_data, oprava) {
this.htmlElement = prekomentar.cloneNode(true);
this.#autor = this.htmlElement.getElementsByClassName('author')[0];
this.#text = this.htmlElement.getElementsByClassName('komtext')[0];
this.id = komentar_data['id'];
this.htmlElement.id = 'k' + this.id;
this.oprava = oprava;
this.oprava.add_komentar_htmlElement(this.htmlElement);
this.update(komentar_data);
this.htmlElement.getElementsByClassName('update-comment')[0].addEventListener('click', _ => this.#update_comment());
this.htmlElement.getElementsByClassName('del-comment')[0].addEventListener('click', _ => this.#delete_comment());
komentare[this.id] = this;
}
update(komentar_data) {
{# this.komentar_data = komentar_data; #}
this.set_autor(komentar_data['autor']);
this.set_text(komentar_data['text']);
};
set_autor(autor) {
this.#autor.textContent=autor;
this.autor = autor;
};
set_text(text) {
this.#text.innerHTML=text;
};
// show comment form when 'update-comment' button pressed
#update_comment() {
return commform.show(this.oprava.img_id, this.oprava.x, this.oprava.y, this.#text.textContent, -1, this.id);
}
#delete_comment() {
if (confirm('Opravdu smazat komentář?')) {
const data = new FormData(CSRF_FORM);
data.append('komentar_id', this.id);
fetch('{% url "korektury_api_komentar_smaz" %}', {method: 'POST', body: data})
.then(response => {
if (!response.ok) {alert('Něco se nepovedlo:' + response.statusText);}
this.smaz_pouze_na_strance();
place_comments();
})
.catch(error => {alert('Něco se nepovedlo:' + error);});
}
}
smaz_pouze_na_strance() {
delete komentare[this.id];
this.htmlElement.remove();
}
}
</script>

View file

@ -1,183 +0,0 @@
{% load static %}
<div id='prepointer' {# id='op{{o.id}}-pointer' #}
class='pointer'
data-highlight='false'
{# data-opravastatus='{{o.status}}' #}
></div>
<div id='preoprava' {# name='op{{o.id}}' id='op{{o.id}}' #}
class='oprava'
{# data-opravastatus='{{o.status}}' #}
data-opravazobrazit='true'
>
<div class='corr-tagy'>
{# {% for tag in o.tagy %} <span style="background:{{ tag.barva }}>{{ tag.text }}<span/> #}
</div>
<div class='corr-body'>
{# {% for k in o.komentare %} {% include "korektury/korekturovatko/__komentar.html" %} {% endfor %} #}
</div>
<div class='corr-header'>
<span class='float-right'>
<span class='corr-buttons'>
<button type='button' style='display: none' class='del' title='Smaž opravu'>
<img src='{% static "korektury/imgs/delete.png"%}' alt='🗑️'/>
</button>
<button type='button' class='action' value='k_oprave' title='Označ jako neopravené'>
<img src='{% static "korektury/imgs/undo.png"%}' alt='↪'/>
</button>
<button type='button' class='action' value='opraveno' title='Označ jako opravené'>
<img src='{% static "korektury/imgs/check.png"%}' alt='✔️'/>
</button>
<button type='button' class='action' value='neni_chyba' title='Označ, že se nebude měnit'>
<img src='{% static "korektury/imgs/cross.png" %}' alt='❌'/>
</button>
<button type='button' class='action' value='k_zaneseni' title='Označ jako připraveno k zanesení'>
<img src='{% static "korektury/imgs/tex.png" %}' alt='TeX'/>
</button>
<a href='{% url "admin:korektury_oprava_change" -1 %}' class='edit' title='Uprav korekturu jako takovou.' style="text-decoration: none;"> {# FIXME Udělat z toho tlačítko? #}
<img src='{% static "korektury/imgs/edit.png"%}' alt='✏️' style="opacity: 0.5;"/> {# FIXME Odlišit jinak než pomocí opacity? #}
</a>
<button type='button' class='komentovat_disabled' title='Korekturu nelze komentovat, protože už je uzavřená' disabled=''>
<img src='{% static "korektury/imgs/comment-gr.png" %}' alt='💭'/>
</button>
<button type='button' class='komentovat' title='Komentovat'>
<img src='{% static "korektury/imgs/comment.png" %}' alt='💭'/>
</button>
</span>
<button type='button' class='toggle-vis' title='Skrýt/Zobrazit'>
<img class='toggle-button' src='{% static "korektury/imgs/hide.png" %}' alt='⬆'/>
</button>
</span>
</div>
</div>
<script>
const preoprava = document.getElementById('preoprava');
const prepointer = document.getElementById('prepointer');
const opravy = {};
class Oprava {
static update_or_create(oprava_data) {
const id = oprava_data['id'];
if (id in opravy) return opravy[id].update(oprava_data);
else return new Oprava(oprava_data);
}
#komentare; #tagy;
htmlElement; pointer;
id; x; y; img_id; status; zobrazit = true; {# oprava_data; #}
constructor(oprava_data) {
this.htmlElement = preoprava.cloneNode(true);
this.pointer = prepointer.cloneNode(true);
this.#komentare = this.htmlElement.getElementsByClassName('corr-body')[0];
this.#tagy = this.htmlElement.getElementsByClassName('corr-tagy')[0];
this.id = oprava_data['id'];
this.htmlElement.id = 'op' + this.id;
this.pointer.id = 'op' + this.id + '-pointer';
this.x = oprava_data['x'];
this.y = oprava_data['y'];
this.img_id = oprava_data['strana'];
this.update(oprava_data);
this.htmlElement.getElementsByClassName('toggle-vis')[0].addEventListener('click', _ => this.#toggle_visibility());
for (const button of this.htmlElement.getElementsByClassName('action'))
button.addEventListener('click', async event => this.#zmenStavKorektury(event));
this.htmlElement.getElementsByClassName('komentovat')[0].addEventListener('click', _ => this.#comment())
this.htmlElement.getElementsByClassName('del')[0].addEventListener('click', _ => this.#delete());
const odkaz_editace = this.htmlElement.getElementsByClassName('edit')[0];
odkaz_editace.href = odkaz_editace.href.replace("-1", this.id);
odkaz_editace.onclick = ev => { if (!confirm("Editace korektury je velmi pokročilá featura umožňující přesouvat korekturu nebo přidávat informované orgy, opravdu chceš pokračovat do adminu?")) ev.preventDefault(); };
this.htmlElement.addEventListener('mouseover', _ => this.pointer.dataset.highlight = 'true');
this.htmlElement.addEventListener('mouseout', _ => this.pointer.dataset.highlight = 'false');
opravy[this.id] = this;
if (this.img_id in comments) comments[this.img_id].push(this); else alert("Někdo korekturoval stranu, která neexistuje. Dejte vědět webařům :)");
}
update(oprava_data) {
{# this.oprava_data = oprava_data; #}
this.set_status(oprava_data['status']);
this.#tagy.innerHTML = "";
for (const tag of oprava_data["tagy"]) {
const span = document.createElement("span");
span.innerHTML = tag["nazev"];
span.classList.add("korektury-tag");
span.style.backgroundColor = tag["barva"];
this.#tagy.appendChild(span);
}
return this;
};
set_status(status) {
this.status = status;
this.htmlElement.dataset.opravastatus=status;
this.pointer.dataset.opravastatus=status;
};
add_komentar_htmlElement(htmlElement) { this.#komentare.appendChild(htmlElement); }
// hide or show text of correction
#toggle_visibility(){
this.zobrazit = !this.zobrazit;
this.htmlElement.dataset.opravazobrazit = String(this.zobrazit);
place_comments()
}
// show comment form, when 'comment' button pressed
#comment() { commform.show(this.img_id, this.x, this.y, "", this.id); }
#zmenStavKorektury(event) {
const data = new FormData(CSRF_FORM);
data.append('id', this.id);
data.append('action', event.target.value);
fetch('{% url "korektury_api_oprava_stav" %}', {method: 'POST', body: data})
.then(response => {
if (!response.ok) {alert('Něco se nepovedlo:' + response.statusText);}
else response.json().then(data => {
this.set_status(data['status']);
updatuj_pocty_stavu();
});
})
.catch(error => {alert('Něco se nepovedlo:' + error);});
}
#delete() {
if (confirm('Opravdu smazat korekturu?')) {
const data = new FormData(CSRF_FORM);
data.append('oprava_id', this.id);
fetch('{% url "korektury_api_oprava_smaz" %}', {method: 'POST', body: data})
.then(response => {
if (!response.ok) {alert('Něco se nepovedlo:' + response.statusText);}
this.#smaz_pouze_na_strance()
updatuj_pocty_stavu();
updatuj_pocty_zasluh();
place_comments();
})
.catch(error => {alert('Něco se nepovedlo:' + error);});
}
}
#smaz_pouze_na_strance() {
comments[this.img_id].splice(comments[this.img_id].indexOf(this), 1);
delete opravy[this.id];
for (const komentar of Object.values(komentare)) if (komentar.oprava === this) komentar.smaz_pouze_na_strance();
this.htmlElement.remove();
this.pointer.remove();
}
}
</script>

View file

@ -1,54 +0,0 @@
{% for i in img_indexes %}
<div class='imgdiv'>
<img
id='img-{{i}}'
width='1021' height='1448'
src='/media/korektury/img/{{korekturovanepdf.get_prefix}}-{{i}}.png'
alt='Strana {{ i|add:1 }}'
class="strana"
/>
</div>
<hr/>
{% endfor %}
<script>
// Mapování stránka -> korektury
/**
* @type {Object.<number, Array<Oprava>>}
*/
const comments = {
{% for s in img_indexes %}
{{s}}: []{% if not forloop.last %},{% endif %}
{% endfor %}
};
// show comment form, when clicked to image
for (const image of document.getElementsByClassName('strana')) {
image.addEventListener('click', ev => {
switch (document.body.dataset.status) {
case 'zanaseni':
if (!confirm('Právě jsou zanášeny korektury, opravdu chcete přidat novou?'))
return;
break;
case 'zastarale':
if (!confirm('Toto PDF je již zastaralé, opravdu chcete vytvořit korekturu?'))
return;
break;
}
let dx, dy;
const par = image.parentNode;
if (ev.pageX != null) {
dx = ev.pageX - par.offsetLeft;
dy = ev.pageY - par.offsetTop;
} else { //IE a další
dx = ev.offsetX;
dy = ev.offsetY;
}
const img_id = image.id.substring(4);
commform.show(img_id, dx, dy, '');
console.log("Pro přesun korektur: strana = " + img_id + ", x = " + dx + ", y = " + dy);
});
}
</script>

View file

@ -1,58 +0,0 @@
{% include "korektury/korekturovatko/__edit_komentar.html" %}
{% include "korektury/korekturovatko/__stranky.html" %}
{# {% for o in opravy %} {% include "korektury/korekturovatko/__oprava.html" %} {% endfor %} #}
{% include "korektury/korekturovatko/__oprava.html" %}
{% include "korektury/korekturovatko/__komentar.html" %}
{% include "korektury/korekturovatko/__dalsi_korektura.html" %}
<button type="button" id="korektury-aktualizace">Aktualizuj korektury.</button>
<script>
/**
*
* @param {RequestInit} data
* @param {Boolean} catchError
* @param pri_uspechu Akce, která se má provést při úspěchu (speciálně zavřít formulář)
*/
function update_all(data={}, catchError=true, pri_uspechu=null) { // FIXME není mi jasné, zda v {} nemá být `cache: "no-store"`, aby prohlížeč necachoval GET.
fetch('{% url "korektury_api_opravy_a_komentare" korekturovanepdf.id %}', data)
.then(response => {
if (!response.ok && catchError) {alert('Něco se nepovedlo:' + response.statusText);}
else response.json().then(data => {
for (const oprava_data of data["context"]) {
const oprava = Oprava.update_or_create(oprava_data);
for (const komentar_data of oprava_data["komentare"]) {
Komentar.update_or_create(komentar_data, oprava);
}
}
updatuj_pocty_stavu();
updatuj_pocty_zasluh();
place_comments();
if (pri_uspechu) pri_uspechu();
});
})
.catch(error => {if (catchError) alert('Něco se nepovedlo:' + error);});
}
window.addEventListener("load", _ => {
update_all({}, true, _ => {
if (location.hash !== "") location.hash = location.hash; // Po rozházení korektur sescrollujeme na kotvu v URL
});
});
// FIXME není mi jasné, zda v {} nemá být `cache: "no-store"`, aby prohlížeč necachoval GET.
document.getElementById("korektury-aktualizace").addEventListener("click", _ => update_all({}, false));
// FIXME není mi jasné, zda v {} nemá být `cache: "no-store"`, aby prohlížeč necachoval GET.
setInterval(() => update_all({}, false), 120000); // Každý dvě minuty fetchni korektury
</script>
<form id='CSRF_form' style='display: none'>{% csrf_token %}</form>
<script>
const CSRF_FORM = document.getElementById('CSRF_form');
</script>

View file

@ -1,68 +0,0 @@
Zobrazit:
<input type="checkbox"
id="k_oprave_checkbox"
name="k_oprave_checkbox"
onchange="toggle_corrections('k_oprave')" checked>
<label for="k_oprave_checkbox">K opravě (<span id="k_oprave_pocet"></span>)</label>
<input type="checkbox"
id="opraveno_checkbox"
name="opraveno_checkbox"
onchange="toggle_corrections('opraveno')" checked>
<label for="opraveno_checkbox">Opraveno (<span id="opraveno_pocet"></span>)</label>
<input type="checkbox"
id="neni_chyba_checkbox"
name="neni_chyba_checkbox"
onchange="toggle_corrections('neni_chyba')" checked>
<label for="neni_chyba_checkbox">Není chyba (<span id="neni_chyba_pocet"></span>)</label>
<input type="checkbox"
id="k_zaneseni_checkbox"
name="k_zaneseni_checkbox"
onchange="toggle_corrections('k_zaneseni')" checked>
<label for="k_zaneseni_checkbox">K zanesení (<span id="k_zaneseni_pocet"></span>)</label>
<hr/>
<script>
const spany_s_pocty_stavu = {
'k_oprave': document.getElementById('k_oprave_pocet'),
'opraveno': document.getElementById('opraveno_pocet'),
'neni_chyba': document.getElementById('neni_chyba_pocet'),
'k_zaneseni': document.getElementById('k_zaneseni_pocet'),
}
function toggle_corrections(aclass)
{
const stylesheets = document.styleSheets;
let ssheet = null;
for (let i=0; i<stylesheets.length; i++){
if (stylesheets[i].title === "opraf-css"){
ssheet = stylesheets[i];
break;
}
}
if (! ssheet){
return;
}
for (let i=0; i<ssheet.cssRules.length; i++){
const rule = ssheet.cssRules[i];
if (rule.selectorText === '[data-opravastatus="'+aclass+'"]'){
if (rule.style.display === ""){
rule.style.display = "none";
} else {
rule.style.display = "";
}
}
}
place_comments();
}
function updatuj_pocty_stavu() {
const pocty_stavu = {};
for (const stav of Object.keys(spany_s_pocty_stavu)) pocty_stavu[stav] = 0;
for (const oprava of Object.values(opravy)) {
if (!(oprava.status in pocty_stavu)) pocty_stavu[oprava.status] = 0;
pocty_stavu[oprava.status] += 1;
}
for (let [stav, pocet] of Object.entries(pocty_stavu)) spany_s_pocty_stavu[stav].innerText = pocet;
}
</script>

View file

@ -1,44 +0,0 @@
<h4>Změnit stav PDF:</h4>
<i>Aktuální: {{korekturovanepdf.status}}</i>
<br>
<form method="post" id="PDFSTAV_FORM">
{% csrf_token %}
<input type="radio" name="state" value="{{ korekturovanepdf.STATUS.PRIDAVANI }}" {% if korekturovanepdf.status == korekturovanepdf.STATUS.PRIDAVANI %} checked {% endif %}>Přidávání korektur
<br>
<input type="radio" name="state" value="{{ korekturovanepdf.STATUS.ZANASENI }}" {% if korekturovanepdf.status == korekturovanepdf.STATUS.ZANASENI %} checked {% endif %}>Zanášení korektur
<br>
<input type="radio" name="state" value="{{ korekturovanepdf.STATUS.ZASTARALE }}" {% if korekturovanepdf.status == korekturovanepdf.STATUS.ZASTARALE %} checked {% endif %}>Zastaralé, nekorigovat
<br>
<input type='submit' value='Změnit stav PDF'/>
</form>
<script>
const pdfstav_form = document.getElementById('PDFSTAV_FORM');
/**
*
* @param {RequestInit} data
* @param {Boolean} catchError
*/
function fetchStav(data, catchError=true) {
fetch("{% url 'korektury_api_pdf_stav' korekturovanepdf.id %}", data
)
.then(response => {
if (!response.ok) { if (catchError) alert("Něco se nepovedlo:" + response.statusText);}
else response.json().then(data => document.body.dataset.status = data["status"]);
})
.catch(error => {if (catchError) alert("Něco se nepovedlo:" + error);});
}
pdfstav_form.addEventListener('submit', async event => {
event.preventDefault();
const data = new FormData(pdfstav_form);
fetchStav({method: "POST", body: data});
});
// FIXME není mi jasné, zda v {} nemá být `cache: "no-store"`, aby prohlížeč necachoval get.
setInterval(() => fetchStav({}, false), 120000); // Každý dvě minuty fetchni stav
</script>

View file

@ -1,61 +0,0 @@
{% load static %}
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" title="opraf-css" type="text/css" media="screen, projection" href="{% static "korektury/opraf.css"%}?version=1" />
<link href="{% static 'css/rozliseni.css' %}?version=1" rel="stylesheet">
<script src="{% static "korektury/opraf.js"%}?version=1"></script>
<title>Korektury {{korekturovanepdf.nazev}}</title>
</head>
<body class="{{ LOCAL_TEST_PROD }}web" data-status="{{ korekturovanepdf.status }}">
<h1>Korektury {{korekturovanepdf.nazev}}</h1>
<h2 class="textzanaseni"> Probíhá zanášení korektur, zvažte, zda chcete přidávat nové </h2>
<h2 class="textzastarale"> Toto PDF je již zastaralé, nepřidávejte nové korektury </h2>
<i>{{korekturovanepdf.komentar}}</i>
<br>
<i>Klikni na chybu, napiš komentář</i> |
<a href="{{korekturovanepdf.pdf.url}}">stáhnout PDF (bez korektur)</a> |
<a href="../">seznam souborů</a> |
<a href="/admin/korektury/korekturovanepdf/">Spravovat PDF</a> |
<a href="../help">nápověda</a> |
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|
<a href="/">hlavní stránka</a> |
<a href="https://mam.mff.cuni.cz/wiki">wiki</a> |
<hr />
{% include "korektury/korekturovatko/_schovani_korektur.html" %}
{% include "korektury/korekturovatko/_main.html" %}
{% include "korektury/korekturovatko/_zmena_stavu.html" %}
<hr/>
<p>
Děkujeme opravovatelům: <span id="pocty_autoru"></span></p>
<hr>
<script>
const span_s_pocty_autoru = document.getElementById("pocty_autoru")
function updatuj_pocty_zasluh() {
const pocty_autoru = {};
for (let komentar of Object.values(komentare)) {
if (!(komentar.autor in pocty_autoru)) pocty_autoru[komentar.autor] = 0;
pocty_autoru[komentar.autor] += 1;
}
const setrizene = [];
for (const keyval of Object.entries(pocty_autoru)) setrizene.push(keyval);
setrizene.sort(function(a, b) {return a[1] - b[1];});
let ans = "";
for (let [autor, pocet] of setrizene) ans += `, ${autor} (${pocet})`;
span_s_pocty_autoru.innerHTML = ans.substring(2);
}
</script>
</body>
</html>

View file

@ -0,0 +1,244 @@
{% load static %}
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" title="opraf-css" type="text/css" media="screen, projection" href="{% static "korektury/opraf.css"%}" />
<link href="{% static 'css/rozliseni.css' %}?version=1" rel="stylesheet">
<script src="{% static "korektury/opraf.js"%}"></script>
<title>Korektury {{pdf.nazev}}</title>
</head>
<body class="{{ LOCAL_TEST_PROD }}web{% if pdf.status == 'zanaseni'%} comitting{% elif pdf.status == 'zastarale' %} deprecated{% endif %}" onload='place_comments()'>
<h1>Korektury {{pdf.nazev}}</h1>
{% if pdf.status == 'zanaseni' %} <h2> Probíhá zanášení korektur, zvažte, zda chcete přidávat nové </h2> {% endif %}
{% if pdf.status == 'zastarale' %} <h2> Toto PDF je již zastaralé, nepřidávejte nové korektury </h2> {% endif %}
<i>{{pdf.komentar}}</i>
<br>
<i>Klikni na chybu, napiš komentář</i> |
<a href="{{pdf.pdf.url}}">stáhnout PDF (bez korektur)</a> |
<a href="../">seznam souborů</a> |
<a href="/admin/korektury/korekturovanepdf/">Spravovat PDF</a> |
<a href="../help">nápověda</a> |
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|
<a href="/">hlavní stránka</a> |
<a href="https://mam.mff.cuni.cz/wiki">wiki</a> |
<hr />
Zobrazit:
<input type="checkbox"
id="k_oprave_checkbox"
name="k_oprave_checkbox"
onchange="toggle_corrections('k_oprave')" checked>
<label for="k_oprave_checkbox">K opravě ({{k_oprave_cnt}})</label>
<input type="checkbox"
id="opraveno_checkbox"
name="opraveno_checkbox"
onchange="toggle_corrections('opraveno')" checked>
<label for="opraveno_checkbox">Opraveno ({{opraveno_cnt}})</label>
<input type="checkbox"
id="neni_chyba_checkbox"
name="neni_chyba_checkbox"
onchange="toggle_corrections('neni_chyba')" checked>
<label for="neni_chyba_checkbox">Není chyba ({{neni_chyba_cnt}})</label>
<input type="checkbox"
id="k_zaneseni_checkbox"
name="k_zaneseni_checkbox"
onchange="toggle_corrections('k_zaneseni')" checked>
<label for="k_zaneseni_checkbox">K zanesení ({{k_zaneseni_cnt}})</label>
<hr/>
<div id="commform-div">
<!-- Pridat korekturu / komentar !-->
<form action='' onsubmit='save_scroll(this)' id="commform" method="POST">
{% csrf_token %}
<input size="24" name="au" value="{{user.first_name}} {{user.last_name}}" readonly/>
<input type=submit value="Oprav!"/>
<button type="button" onclick="close_commform()">Zavřít</button>
<br/>
<textarea onkeypress="textarea_onkey(event);" id="commform-text" cols=40 rows=10 name="txt"></textarea>
<br/>
<input type="hidden" size="3" name="pdf" value='{{pdf.id}}'/>
<input type="hidden" size="3" id="commform-x" name="x"/>
<input type="hidden" size="3" id="commform-y" name="y"/>
<input type="hidden" size="3" id="commform-img-id" name="img-id"/>
<input type="hidden" size="3" id="commform-id" name="id"/>
<input type="hidden" size="3" id="commform-action" name="action"/>
<input type="hidden" size="3" id="commform-action" name="scroll"/>
</form>
<!-- /Pridat korekturu / komentar !-->
</div>
{% for i in img_indexes %}
<div class='imgdiv'>
<img width='1021' height='1448'
onclick='img_click(this,event)' id='img-{{i}}'
src='/media/korektury/img/{{img_prefix}}-{{i}}.png'/>
</div>
<hr/>
{% endfor %}
<h4>Změnit stav PDF:</h4>
<i>Aktuální: {{pdf.status}}</i>
<br>
<!-- Zmenit stav PDF !-->
<form method="post">
{% csrf_token %}
<input type='hidden' name='action' value='set-state'/>
<input type='hidden' name='pdf' value='{{pdf.id}}'/>
<input type="radio" name="state" value="adding" {% if pdf.status == 'pridavani' %} checked {% endif %}>Přidávání korektur
<br>
<input type="radio" name="state" value="comitting" {% if pdf.status == 'zanaseni' %} checked {% endif %}>Zanášení korektur
<br>
<input type="radio" name="state" value="deprecated" {% if pdf.status == 'zastarale' %} checked {% endif %}>Zastaralé, nekorigovat
<br>
<input type='submit' value='Změnit stav PDF'/>
</form>
<!-- /Zmenit stav PDF !-->
<hr/>
<p>
Děkujeme opravovatelům:
{% for z in zasluhy %}
{{z.autor}} ({{z.pocet}}){% if not forloop.last %},{% endif %}
{% endfor %}</p>
<hr>
{% for o in opravy %}
<div onclick='img_click(this,event)'
id='op{{o.id}}-pointer'
class='pointer {{o.status}}'>
</div>
<div name='op{{o.id}}' id='op{{o.id}}'
class='box {{o.status}}'
onmouseover='box_onmouseover(this)'
onmouseout='box_onmouseout(this)'>
<div class='corr-header'>
<span class='author' id='op{{o.id}}-autor'>{{o.autor}}</span>
<span class='float-right'>
<span id='op{{o.id}}-buttons'>
<!-- Existujici korektura !-->
<form action='' onsubmit='save_scroll(this)' method='POST'>
{% csrf_token %}
<input type='hidden' name="au" value="{{o.autor}}"/>
<input type='hidden' name='pdf' value='{{pdf.id}}'>
<input type='hidden' name='id' value='{{o.id}}'>
<input type='hidden' name='scroll'>
{% if o.komentare %}
<button name='action' value='del' type='button'
title="Opravu nelze smazat &ndash; už ji někdo okomentoval">
<img src="{% static "korektury/imgs/delete-gr.png"%}"/>
</button>
{% else %}
<button type='submit' name='action' value='del' title='Smaž opravu'>
<img src="{% static "korektury/imgs/delete.png"%}"/>
</button>
{% endif %}
{% if o.status != 'k_oprave' %}
<button type='submit' name='action' value='undone' title='Označ jako neopravené'>
<img src="{% static "korektury/imgs/undo.png"%}"/>
</button>
{% endif %}
{% if o.status != 'opraveno' %}
<button type='submit' name='action' value='done' title='Označ jako opravené'>
<img src="{% static "korektury/imgs/check.png"%}"/>
</button>
{% endif %}
{% if o.status != 'neni_chyba' %}
<button type='submit' name='action' value='wontfix' title='Označ, že se nebude měnit'>
<img src="{% static "korektury/imgs/cross.png" %}"/>
</button>
{% endif %}
{% if o.status != 'k_zaneseni' %}
<button type='submit' name='action' value='ready' title='Označ jako připraveno k zanesení'>
<img src="{% static "korektury/imgs/tex.png" %}"/>
</button>
{% endif %}
</form>
<!-- /Existujici korektura !-->
{% if o.komentare %}
<button type='button' title="Korekturu nelze upravit &ndash; už ji někdo okomentoval">
<img src="{% static "korektury/imgs/edit-gr.png" %}"/>
</button>
{% else %}
<button type='button' onclick='box_edit("op{{o.id}}","update");' title='Oprav opravu'>
<img src="{% static "korektury/imgs/edit.png" %}"/>
</button>
{% endif %}
{% if o.status == 'opraveno' or o.status == 'neni_chyba' %}
<button type='button' title='Korekturu nelze komentovat, protože už je uzavřená'>
<img src="{% static "korektury/imgs/comment-gr.png" %}"/>
</button>
{% else %}
<button type='button' onclick='box_edit("op{{o.id}}", "comment");' title='Komentovat'>
<img src="{% static "korektury/imgs/comment.png" %}"/>
</button>
{% endif %}
</span>
<button type='button' onclick='toggle_visibility("op{{o.id}}");' title='Skrýt/Zobrazit'>
<img src="{% static "korektury/imgs/hide.png" %}"/>
</button>
</span>
</div>
<div class='corr-body' id='op{{o.id}}-body'>
<div id='op{{o.id}}-text'>{{o.text|linebreaks}}</div>
{% for k in o.komentare %}
<hr>
<div class='comment' id='k{{k.id}}'>
<div class='corr-header'>
<div class='author'>{{k.autor}}</div>
<div class="float-right">
<!-- Komentar !-->
<form action='' onsubmit='save_scroll(this)' method='POST'>
{% csrf_token %}
<input type='hidden' name='pdf' value='{{pdf.id}}'>
<input type='hidden' name='id' value='{{k.id}}'>
<input type='hidden' name='scroll'>
{% if forloop.last %}
<button type='submit' name='action' value='del-comment' title='Smaž komentář'
onclick='return confirm("Opravdu smazat komentář?")'>
<img src="{% static "korektury/imgs/delete.png" %}"/>
</button>
{% else %}
<button name='action' value='del-comment' type='button'
title="Komentář nelze smazat &ndash; existuje novější">
<img src="{% static "korektury/imgs/delete-gr.png"%}"/>
</button>
{% endif %}
</form>
<!-- /Komentar !-->
{% if forloop.last %}
<button type='button' onclick="update_comment('op{{o.id}}','kt{{k.id}}');" title='Uprav komentář'>
<img src="{% static "korektury/imgs/edit.png"%}"/>
</button>
{% else %}
<button type='button' title="Komentář nelze upravit &ndash; existuje novější">
<img src="{% static "korektury/imgs/edit-gr.png" %}"/>
</button>
{% endif %}
</div>
</div>
<div id='kt{{k.id}}'>{{k.text|linebreaks}}</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
<script>
var comments = [
{% for s in opravy_strany %}
["img-{{s.strana}}", [{% for o in s.op_id %}["op{{o.id}}",{{o.x}},{{o.y}}],{% endfor %}[]]],
{% endfor %}
[]]
{% if scroll %}
window.scrollTo(0,{{scroll}});
{% endif %}
</script>
</body>
</html>

View file

@ -1,6 +1,4 @@
from django.urls import path
from django.urls import include
from personalni.utils import org_required
from . import views
@ -9,6 +7,4 @@ urlpatterns = [
path('korektury/neseskupene/', org_required(views.KorekturyAktualniListView.as_view()), name='korektury_neseskupene_list'),
path('korektury/zastarale/', org_required(views.KorekturyZastaraleListView.as_view()), name='korektury_stare_list'),
path('korektury/<int:pdf>/', org_required(views.KorekturyView.as_view()), name='korektury'),
path('korektury/api/', include('korektury.api.urls')),
]

View file

@ -1,53 +0,0 @@
from django.core.mail import EmailMessage
from django.http import HttpRequest
from django.urls import reverse
from korektury.models import Komentar, Oprava
from personalni.models import Organizator
def send_email_notification_komentar(oprava: Oprava, autor: Organizator, request: HttpRequest):
''' Rozesle e-mail pri pridani komentare / opravy,
ktery obsahuje text vlakna opravy.
'''
# parametry e-mailu
#odkaz = "https://mam.mff.cuni.cz/korektury/{}/".format(oprava.pdf.pk)
odkaz = request.build_absolute_uri(reverse('korektury', kwargs={'pdf': oprava.pdf.pk}))
odkaz = f"{odkaz}#op{oprava.id}-pointer"
from_email = 'korekturovatko@mam.mff.cuni.cz'
subject = 'Nová korektura od {} v {}'.format(autor, oprava.pdf.nazev)
texty = []
for kom in Komentar.objects.filter(oprava=oprava):
texty.append((kom.autor.osoba.plne_jmeno(),kom.text))
optext = "\n\n\n".join([": ".join(t) for t in texty])
text = u"Text komentáře:\n\n{}\n\n=== Konec textu komentáře ===\n\
\nodkaz do korekturovátka: {}\n\
\nVaše korekturovátko\n".format(optext, odkaz)
# Prijemci e-mailu
emails = set()
# nalezeni e-mailu na autory komentaru
for org in oprava.informovani_orgove.all():
email_komentujiciho = org.osoba.email
if email_komentujiciho:
emails.add(email_komentujiciho)
# zodpovedni orgove
for org in oprava.pdf.orgove.all():
email_zobpovedny = org.osoba.email
if email_zobpovedny:
emails.add(email_zobpovedny)
# odstran e-mail autora opravy
email = autor.osoba.email
if email:
emails.discard(email)
EmailMessage(
subject=subject,
body=text,
from_email=from_email,
to=list(emails),
).send()

View file

@ -1,15 +1,25 @@
from django.shortcuts import get_object_or_404, render
from django.views import generic
from django.conf import settings
from django.http import HttpResponseForbidden
from django.core.mail import EmailMessage
from django.db.models import Count,Q
from .models import Oprava, KorekturovanePDF, KorekturaTag
from .models import Oprava,Komentar,KorekturovanePDF, Organizator
from .forms import OpravaForm
import subprocess
import shutil
import os
class KorekturyListView(generic.ListView):
model = KorekturovanePDF
# Nefunguje, filtry se vubec nepouziji
queryset = KorekturovanePDF.objects.annotate(
k_oprave_cnt=Count('oprava',distinct=True,filter=Q(oprava__status=Oprava.STATUS.K_OPRAVE)),
opraveno_cnt=Count('oprava',distinct=True,filter=Q(oprava__status=Oprava.STATUS.OPRAVENO)),
neni_chyba_cnt=Count('oprava',distinct=True,filter=Q(oprava__status=Oprava.STATUS.NENI_CHYBA)),
k_zaneseni_cnt=Count('oprava',distinct=True,filter=Q(oprava__status=Oprava.STATUS.K_ZANESENI)),
k_oprave_cnt=Count('oprava',distinct=True,filter=Q(oprava__status='k_oprave')),
opraveno_cnt=Count('oprava',distinct=True,filter=Q(oprava__status='opraveno')),
neni_chyba_cnt=Count('oprava',distinct=True,filter=Q(oprava__status='neni_chyba')),
k_zaneseni_cnt=Count('oprava',distinct=True,filter=Q(oprava__status='k_zaneseni')),
)
template_name = 'korektury/seznam.html'
@ -46,14 +56,191 @@ class KorekturySeskupeneListView(KorekturyAktualniListView):
return reversed(sorted(qs, key=lambda it: it.cislo_a_tema))
### Korektury
class KorekturyView(generic.DetailView):
model = KorekturovanePDF
pk_url_kwarg = "pdf"
template_name = 'korektury/korekturovatko/htmlstrana.html'
class KorekturyView(generic.TemplateView):
model = Oprava
template_name = 'korektury/opraf.html'
form_class = OpravaForm
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
q = request.POST
scroll = q.get('scroll')
# prirazeni autora podle prihlaseni
autor_user = request.user
# pokud existuje ucet (user), ale neni to organizator = 403
autor = Organizator.objects.filter(osoba__user=autor_user).first()
if not autor:
return HttpResponseForbidden()
if not scroll:
scroll = 0
action = q.get('action')
if (action == ''): # Přidej
x = int(q.get('x'))
y = int(q.get('y'))
text = q.get('txt')
strana = int(q.get('img-id')[4:])
pdf = KorekturovanePDF.objects.get(id=q.get('pdf'))
op = Oprava(x=x,y=y, autor=autor, text=text, strana=strana,pdf = pdf)
op.save()
self.send_email_notification_komentar(op,autor)
elif (action == 'del'):
id = int(q.get('id'))
op = Oprava.objects.get(id=id)
op.delete()
elif (action == 'update'):
id = int(q.get('id'))
op = Oprava.objects.get(id=id)
text = q.get('txt')
op.autor = autor
op.text = text
op.save()
elif (action == 'undone'):
id = int(q.get('id'))
op = Oprava.objects.get(id=id)
op.status = op.STATUS_K_OPRAVE
op.save()
elif (action == 'done'):
id = int(q.get('id'))
op = Oprava.objects.get(id=id)
op.status = op.STATUS_OPRAVENO
op.save()
elif (action == 'ready'):
id = int(q.get('id'))
op = Oprava.objects.get(id=id)
op.status = op.STATUS_K_ZANESENI
op.save()
elif (action == 'wontfix'):
id = int(q.get('id'))
op = Oprava.objects.get(id=id)
op.status = op.STATUS_NENI_CHYBA
op.save()
elif (action == 'comment'):
id = int(q.get('id'))
op = Oprava.objects.get(id=id)
text = q.get('txt')
kom = Komentar(oprava=op,autor=autor,text=text)
kom.save()
self.send_email_notification_komentar(op,autor)
elif (action == 'update-comment'):
id = int(q.get('id'))
kom = Komentar.objects.get(id=id)
text = q.get('txt')
kom.text = text
kom.autor = autor
kom.save()
elif (action == 'del-comment'):
id = int(q.get('id'))
kom = Komentar.objects.get(id=id)
kom.delete()
elif (action == 'set-state'):
pdf = KorekturovanePDF.objects.get(id=q.get('pdf'))
if (q.get('state') == 'adding'):
pdf.status = pdf.STATUS_PRIDAVANI
elif (q.get('state') == 'comitting'):
pdf.status = pdf.STATUS_ZANASENI
elif (q.get('state') == 'deprecated'):
pdf.status = pdf.STATUS_ZASTARALE
pdf.save()
context = self.get_context_data()
context['scroll'] = scroll
context['autor'] = autor
return render(request, 'korektury/opraf.html',context)
def send_email_notification_komentar(self, oprava, autor):
''' Rozesle e-mail pri pridani komentare / opravy,
ktery obsahuje text vlakna opravy.
'''
# parametry e-mailu
#odkaz = "https://mam.mff.cuni.cz/korektury/{}/".format(oprava.pdf.pk)
from django.urls import reverse
odkaz = self.request.build_absolute_uri(reverse('korektury', kwargs={'pdf': oprava.pdf.pk}))
odkaz = f"{odkaz}#op{oprava.id}-pointer"
from_email = 'korekturovatko@mam.mff.cuni.cz'
subject = 'Nová korektura od {} v {}'.format(autor, oprava.pdf.nazev)
texty = [(oprava.autor.osoba.plne_jmeno(),oprava.text)]
for kom in Komentar.objects.filter(oprava=oprava):
texty.append((kom.autor.osoba.plne_jmeno(),kom.text))
optext = "\n\n\n".join([": ".join(t) for t in texty])
text = u"Text komentáře:\n\n{}\n\n=== Konec textu komentáře ===\n\
\nodkaz do korekturovátka: {}\n\
\nVaše korekturovátko\n".format(optext, odkaz)
# Prijemci e-mailu
emails = set()
# e-mail autora korektury
email = oprava.autor.osoba.email
if email:
emails.add(email)
# nalezeni e-mailu na autory komentaru
for komentar in oprava.komentar_set.all():
email_komentujiciho = komentar.autor.osoba.email
if email_komentujiciho:
emails.add(email_komentujiciho)
# zodpovedni orgove
for org in oprava.pdf.orgove.all():
email_zobpovedny = org.osoba.email
if email_zobpovedny:
emails.add(email_zobpovedny)
# odstran e-mail autora opravy
email = autor.osoba.email
if email:
emails.discard(email)
EmailMessage(
subject=subject,
body=text,
from_email=from_email,
to=list(emails),
).send()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['img_indexes'] = range(self.object.stran)
context['tagy'] = KorekturaTag.objects.all()
pdf = get_object_or_404(KorekturovanePDF, id=self.kwargs['pdf'])
context['pdf'] = pdf
context['img_prefix'] = pdf.get_prefix()
context['img_path'] = settings.KOREKTURY_IMG_DIR
context['img_indexes'] = range(pdf.stran)
context['form_oprava'] = OpravaForm()
opravy = Oprava.objects.filter(pdf=self.kwargs['pdf'])
zasluhy = {}
for o in opravy:
if o.autor in zasluhy:
zasluhy[o.autor]+=1
else:
zasluhy[o.autor]=1
o.komentare = o.komentar_set.all()
for k in o.komentare:
if k.autor in zasluhy:
zasluhy[k.autor] += 1
else:
zasluhy[k.autor] = 1
zasluhy = [
{'autor': jmeno, 'pocet': pocet}
for (jmeno, pocet) in zasluhy.items()
]
zasluhy.sort(key=lambda z: z['pocet'], reverse=True)
strany = set(o.strana for o in opravy)
opravy_na_stranu = [{'strana': s, 'op_id': opravy.filter(strana=s)} for s in strany]
context['opravy_strany'] = opravy_na_stranu
context['k_oprave_cnt'] = opravy.filter(status='k_oprave').count()
context['opraveno_cnt'] = opravy.filter(status='opraveno').count()
context['neni_chyba_cnt'] = opravy.filter(status='neni_chyba').count()
context['k_zaneseni_cnt'] = opravy.filter(status='k_zaneseni').count()
context['opravy'] = opravy
context['zasluhy'] = zasluhy
return context
def form_valid(self,form):
return super().form_valid(form)

View file

@ -129,15 +129,12 @@ INSTALLED_APPS = (
'rest_framework',
'rest_framework.authtoken',
'colorfield',
# MaMweb
'mamweb',
'seminar',
'tvorba',
'galerie',
'korektury',
'korektury.api',
'prednasky',
'header_fotky',
'various',

View file

@ -1,3 +0,0 @@
.add-related, .delete-related, .change-related {
display: none;
}

View file

@ -435,7 +435,6 @@ body.localweb, body.testweb, body.suprodweb {
height: 100%;
top: 0;
z-index: -1000;
opacity: 0.7;
}
&:before { left: 0; }

View file

@ -1,20 +0,0 @@
/**** ROZLIŠENÍ MEZI LOKÁLNÍM, TESTOVACÍM A PRODUKČNÍM WEBEM ****/
body.localweb, body.testweb, body.suprodweb {
&:before, &:after {
content: "";
position: fixed;
width: 20px;
height: 100%;
top: 0;
z-index: -1000;
opacity: 0.7;
}
&:before { left: 0; }
&:after { right: 0; }
}
body.localweb { &:before, &:after { background: greenyellow; } }
body.testweb { &:before, &:after { background: darkorange; } }
body.suprodweb { &:before, &:after { background: red; } }
/****************************************************************/

View file

@ -5,7 +5,6 @@
<link rel="shortcut icon" href="{% static 'favicon.ico' %}" type="image/x-icon">
<script src="{% static 'js/jquery-1.11.1.js' %}"></script>
<link href="{% static 'css/rozliseni.css' %}?version=1" rel="stylesheet">
<link href="{% static 'css/admin.css' %}?version=1" rel="stylesheet">
{% endblock %}
{% block bodyclass %}{{ LOCAL_TEST_PROD }}web{% endblock %}

View file

@ -1,18 +0,0 @@
# Generated by Django 4.2.16 on 2025-02-09 21:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('prednasky', '0021_alter_hlasovanioznalostech_odpoved'),
]
operations = [
migrations.AlterField(
model_name='hlasovanioznalostech',
name='odpoved',
field=models.IntegerField(choices=[(1, 'Tohle celkem umím'), (0, 'Už jsem o tom slyšel, ale neřekl bych, že to úplně umím'), (-1, 'Tohle vůbec neznám')], verbose_name='odpověď'),
),
]

View file

@ -1,20 +0,0 @@
# Generated by Django 4.2.16 on 2025-02-19 17:31
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('personalni', '0019_rename_upozorneni_resitel_upozornovat_na_opravy_reseni'),
('prednasky', '0022_preklep_u_odpovedi_hlasovanioznalostech'),
]
operations = [
migrations.AddField(
model_name='hlasovani',
name='ucastnik_osoba',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='personalni.osoba'),
),
]

View file

@ -88,7 +88,6 @@ class Hlasovani(models.Model):
#: že všechna předchozí hlasování zde mají náhodný string…)
#: TODO Změnit to na Osobu*
ucastnik = models.CharField("Účastník", max_length=100)
ucastnik_osoba = models.ForeignKey(Osoba, on_delete=models.CASCADE, blank=False, null=True)
seznam = models.ForeignKey(Seznam, null=True, on_delete=models.SET_NULL)
def __str__(self):
@ -122,7 +121,7 @@ class HlasovaniOZnalostech(models.Model):
class Odpoved(models.IntegerChoices):
""" Na kolik danou znalost účastník ovládá v daném Hlasování (větší číslo = víc zná) """
UMIM = 1, "Tohle celkem umím"
CIRCA = 0, "Už jsem o tom slyšel, ale neřekl bych, že to úplně umím"
CIRCA = 0, "Už jsem o tom slyšel, ale neřekl bychm, že to úplně umím"
NEUMIM = -1, "Tohle vůbec neznám"
odpoved = models.IntegerField(u"odpověď", choices=Odpoved.choices, blank=False, null=False) #: :py:class:`Odpověď <prednasky.models.Prednaska.Odpoved>` na HlasováníOZnalostech

View file

@ -14,29 +14,25 @@
<p>Obtížnost 1 je nejlehčí, 3 nejtěžší.</p>
{{ form_set_prednasky.management_form }}
{% for f, p in formy_a_prednasky %}
<div class="hlasovani-prednaska">
<h4>{{p.nazev}} ({{p.org}})</h4>
<p class="textprednasky">{{p.anotace | linebreaksbr}}</p>
<p class="textprednasky">{{p.anotace}}</p>
<label>Obor: </label> {{p.obor}}<br>
<label>Obtížnost: </label> {{p.obtiznost}}<br>
{% if p.klicova %}<label>Klíčová slova: </label> {{p.klicova}}<br>{% endif%}
<br>
{{ f }}
<br>
</div>
{% empty %}
Nejsou žádné přednášky o kterých by šlo hlasovat.
{% endfor %}
{{ form_set_znalosti.management_form }}
{% for f, z in formy_a_znalosti %}
<div class="hlasovani-znalost">
{% if forloop.first %}<hr/><h3>Jak moc znáš následující?</h3>{% endif %}
<h4>{{z.nazev}}</h4>
<p class="textznalosti">{{z.text | linebreaksbr}}</p>
<p class="textznalosti">{{z.text}}</p>
{{ f }}
<br>
</div>
{% endfor %}
<input type="submit" value="Odeslat"/>
</form>

View file

@ -2,19 +2,19 @@
{% block content %}
<h1>{% block nadpis1a %}
Výsledky hlasování o přednáškách
Hlasování o přednáškách
{% endblock %}</h1>
{# Projdi vsechny seznamy #}
<div class="mam-org-only">
<ul>
{% for seznam in object_list %}
<li>
{% if seznam.stav == seznam.Stav.NAVRH %}
Návrh přednášek na soustředění {{seznam.soustredeni.misto}}
{% if seznam.stav == 1 %} {# STAV_NAHRH = 1 #}
<a href="/prednasky/seznam_prednasek/{{seznam.id}}">Návrh přednášek na soustředění {{seznam.soustredeni.misto}} </a>
{% else %}
Seznam přednášek na soustředění {{seznam.soustredeni.misto}}
<a href="/prednasky/seznam_prednasek/{{seznam.id}}">Seznam přednášek na soustředění {{seznam.soustredeni.misto}} </a>
{% endif %}
<a href='{% url "seznam-export-csv" seznam=seznam.id %}'>(CSV)</a>
<a href="/prednasky/seznam_prednasek/{{seznam.id}}/hlasovani.csv">Export</a>
</li>
{% endfor %}
</ul>

View file

@ -12,9 +12,19 @@ urlpatterns = [
'prednasky/metaseznam_prednasek',
org_required(views.MetaSeznamListView.as_view()),
name='metaseznam-list'),
# path(
# 'prednasky/seznam_prednasek/<int:seznam>/export',
# org_required(views.SeznamExportView),
# name='seznam-export'
# ),
path(
'prednasky/seznam_prednasek/<int:seznam>/hlasovani.csv',
org_required(views.PrednaskyExportView),
name='seznam-export-csv'
),
path(
'prednasky/seznam_prednasek/<int:seznam>/',
org_required(views.SeznamListView.as_view()),
name='seznam-list'
),
]

View file

@ -64,7 +64,6 @@ def newPrednaska(request: HttpRequest) -> HttpResponse:
prednaska=prednaska,
body=form.cleaned_data['body'],
ucastnik=ucastnik,
ucastnik_osoba=osoba,
seznam=seznam,
)
@ -133,6 +132,64 @@ class MetaSeznamListView(generic.ListView):
template_name = 'prednasky/metaseznam_prednasek.html'
class SeznamListView(generic.ListView):
"""
Náhled na to, kolik která přednáška v :py:class:`Seznamu <prednasky.models.Seznam>` :py:class:`hlasů <prednasky.models.Hlasovani.Body>`.
(Je otázka, zda tento View vůbec chceme. Pokud ano, hodilo by se do něj přidat i znalosti.)
"""
template_name = 'prednasky/seznam_prednasek.html'
def get_queryset(self):
self.seznam = get_object_or_404(Seznam, id=self.kwargs["seznam"])
prednasky = Prednaska.objects.filter(seznamy=self.seznam).order_by(
'org__osoba__user__first_name', 'org__osoba__user__last_name'
)
return prednasky
# FIXME nahradit anotaci s filtrem po prechodu na Django 2.2
def get_context_data(self,**kwargs):
context = super(SeznamListView, self).get_context_data(**kwargs)
# hlasovani se vztahuje k nejnovejsimu soustredeni
sous = Soustredeni.objects.first()
seznam = Seznam.objects.filter(soustredeni = sous, stav=Seznam.Stav.NAVRH).first()
for obj in self.object_list:
hlasovani_set = obj.hlasovani_set.filter(seznam=seznam).only('body')
obj.body = sum(map(lambda x: x.body,hlasovani_set))
return context
# def SeznamExportView(request, seznam):
# """Vypíše výsledky hlasování ve formátu pro prologovský optimalizátor"""
# # TODO zřejmě se nepoužívá, časem vyřadit? nahradit tabulkou vhodnější pro
# # lidi?
# hlasovani = Hlasovani.objects.filter(seznam=seznam)
# prednasky = Prednaska.objects.filter(seznamy=seznam)
# orgove = set(p.org for p in prednasky)
# ucastnici = set(h.ucastnik for h in hlasovani)
#
# for p in prednasky:
# p.body = []
# for u in ucastnici:
# try:
# p.body.append(hlasovani.get(ucastnik=u, prednaska=p).body)
# except ObjectDoesNotExist:
# # účastník nehlasoval
# p.body.append("?")
#
# for h in hlasovani:
# h.ucastnik = hash(h.ucastnik)
#
# return render(
# request,
# 'prednasky/seznam_prednasek_export.txt',
# {"hlasovani": hlasovani, "prednasky": prednasky, "orgove": orgove},
# content_type="text/plain"
# )
def PrednaskyExportView(request: HttpRequest, seznam: int, **kwargs) -> HttpResponse:
"""
Vrátí všechna :py:class:`Hlasování <prednasky.models.Hlasovani>`

View file

@ -25,7 +25,6 @@ django_reverse_admin # Lepší handlování OneToOne fieldů v adminu
django-rest-framework
django-webpack-loader
django-rest-polymorphic
django-colorfield # Field pro ukládání barvy (např. tagy v korekturovátku)
# debug tools/extensions

View file

@ -27,7 +27,7 @@ class HromadnePridaniForm(Form):
""" Formulář pro hromadné přidání úložek a problémů """
tema = CharField(label="Název tématu:")
cislo = IntegerField(label="Číslo:", min_value=1)
dil = IntegerField(label="Díl:", min_value=1)
body = CharField(label="Počty bodů (0 pro problém) oddělené čárkami:")
def clean_tema(self):
@ -41,7 +41,7 @@ class HromadnePridaniForm(Form):
def clean_body(self):
""" Kontrola, že `body` je seznam čísel """
try:
list(map(float, self.cleaned_data["body"].split(",")))
list(map(int, self.cleaned_data["body"].split(",")))
except ValueError:
raise ValidationError("Špatný formát bodů")
return self.cleaned_data['body']
@ -64,21 +64,21 @@ class HromadnePridaniView(FormView):
""" Upravený Pavlův skript na hromadné přidání úložek a problémů. """
cd = form.cleaned_data
tema = cd["tema"]
cislo = cd["cislo"]
body = list(map(float, cd["body"].split(",")))
dil = cd["dil"]
body = list(map(int, cd["body"].split(",")))
t = Problem.objects.get(nazev__exact=tema, nadproblem=None)
with transaction.atomic():
pfx = f"{t.nazev}, "
pfx = f"{t.nazev}, díl {dil}, "
for k, b in enumerate(body, 1):
u = Uloha.objects.create(
nadproblem=t,
nazev=pfx + f"{'úloha' if b > 0 else 'problém'} {cislo}.{k}",
nazev=pfx + f"{'úloha' if b > 0 else 'problém'} {k}",
autor=t.autor,
garant=t.garant,
max_body=b,
cislo_zadani=Cislo.get(t.rocnik.rocnik, cislo),
cislo_zadani=Cislo.get(t.rocnik.rocnik, dil),
kod=k,
stav=Problem.STAV_ZADANY,
)