framadate celkem fungujici, nesplnuje design zbytku webu...
This commit is contained in:
parent
174087edc7
commit
479c9eb192
18 changed files with 706 additions and 0 deletions
0
framadate/__init__.py
Normal file
0
framadate/__init__.py
Normal file
3
framadate/admin.py
Normal file
3
framadate/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
framadate/apps.py
Normal file
6
framadate/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class FramadateConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'framadate'
|
54
framadate/migrations/0001_initial.py
Normal file
54
framadate/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
# Generated by Django 4.2.16 on 2025-01-26 17:14
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Framadate',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.TextField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='FramadateDate',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('day', models.DateField()),
|
||||
('time', models.TimeField(blank=True, null=True)),
|
||||
('framadate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='days_set', to='framadate.framadate')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['day', 'time'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NameInFramadate',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('framadate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='names_set', to='framadate.framadate')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Record',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('choice', models.IntegerField(choices=[(0, 'Unknown'), (3, 'Yes'), (2, 'No'), (1, 'Maybe')], default=0)),
|
||||
('framadateDate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='records_set', to='framadate.framadatedate')),
|
||||
('nameInFramadate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='records_set', to='framadate.nameinframadate')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['framadateDate__day', 'framadateDate__time'],
|
||||
},
|
||||
),
|
||||
]
|
0
framadate/migrations/__init__.py
Normal file
0
framadate/migrations/__init__.py
Normal file
59
framadate/models.py
Normal file
59
framadate/models.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
from django.db import models
|
||||
from datetime import datetime
|
||||
|
||||
# Create your models here
|
||||
class Framadate(models.Model):
|
||||
name = models.TextField()
|
||||
|
||||
"isRelevant if all dates are in the future"
|
||||
@property
|
||||
def isRelevant(self):
|
||||
for day in self.days_set.all():
|
||||
if day.day < datetime.now().date():
|
||||
return False
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class NameInFramadate(models.Model):
|
||||
framadate = models.ForeignKey(Framadate, on_delete=models.CASCADE, related_name="names_set")
|
||||
name = models.CharField(max_length=200)
|
||||
|
||||
def __str__(self):
|
||||
return self.name + " - in - " + self.framadate.name
|
||||
|
||||
class FramadateDate(models.Model):
|
||||
framadate = models.ForeignKey(Framadate, on_delete=models.CASCADE, related_name="days_set")
|
||||
day = models.DateField()
|
||||
time = models.TimeField(null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.day.strftime("%Y-%m-%d") + " - in- " + self.framadate.name
|
||||
|
||||
class Meta:
|
||||
ordering = ['day', 'time']
|
||||
|
||||
class Choice(models.IntegerChoices):
|
||||
UNKNOWN = 0
|
||||
YES = 3
|
||||
NO = 2
|
||||
MAYBE = 1
|
||||
|
||||
# Quirk -> name and framadateDate needs to share same Framadate...
|
||||
class Record(models.Model):
|
||||
nameInFramadate = models.ForeignKey(NameInFramadate, on_delete=models.CASCADE, related_name="records_set")
|
||||
framadateDate = models.ForeignKey(FramadateDate, on_delete=models.CASCADE, related_name="records_set")
|
||||
choice = models.IntegerField(choices=Choice.choices, default=Choice.UNKNOWN)
|
||||
|
||||
@property
|
||||
def choiceName(self):
|
||||
return Choice(self.choice).name
|
||||
|
||||
def __str__(self):
|
||||
if self.framadateDate.time:
|
||||
return self.nameInFramadate.name + " - in - " + self.framadateDate.framadate.name + " - " + self.framadateDate.day.strftime("%Y-%m-%d") + " - " + self.framadateDate.time.strftime("%H") + ":00 - " + Choice(self.choice).name
|
||||
return self.nameInFramadate.name + " - in - " + self.framadateDate.framadate.name + " - " + self.framadateDate.day.strftime("%Y-%m-%d") + " - " + Choice(self.choice).name
|
||||
|
||||
class Meta:
|
||||
ordering = ['framadateDate__day', 'framadateDate__time']
|
132
framadate/static/framadate/framadate_detail.css
Normal file
132
framadate/static/framadate/framadate_detail.css
Normal file
|
@ -0,0 +1,132 @@
|
|||
|
||||
textarea {
|
||||
font-family: "Tomorrow", serif;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-size: 1.5em;
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
.framadate-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8em;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.table {
|
||||
overflow-x: auto;
|
||||
padding-bottom: 2em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3em;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
gap: 0.3em;
|
||||
width: max-content;
|
||||
|
||||
.item {
|
||||
/* background-color: red; */
|
||||
width: 140px;
|
||||
text-align: center;
|
||||
padding: 0.8em 0;
|
||||
}
|
||||
}
|
||||
|
||||
.YES {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.NO {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.MAYBE {
|
||||
background-color: goldenrod;
|
||||
}
|
||||
|
||||
.UNKNOWN {
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
.radio-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
|
||||
label {
|
||||
width: 100%;
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label:has(input:checked) {
|
||||
background-color: #2973B2;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
#name {
|
||||
font-family: "Tomorrow", serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.submit {
|
||||
transition: box-shadow 0.2s ease;
|
||||
background-color: #2973B2;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 0.5em 1em;
|
||||
margin: 1em;
|
||||
width: min-content;
|
||||
white-space: nowrap;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
border: none;
|
||||
font-size: 1.4em;
|
||||
font-family: "Tomorrow", serif;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.delete {
|
||||
background-color: red;
|
||||
color: black;
|
||||
|
||||
}
|
||||
|
||||
.submit:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mini-row .item {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.item-with-edit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1em;
|
||||
|
||||
a {
|
||||
color: #2973B2;
|
||||
}
|
||||
}
|
||||
|
||||
.delete-framadate {
|
||||
font-size: 0.5em;
|
||||
margin-left: 2em;
|
||||
color: red;
|
||||
}
|
82
framadate/static/framadate/new_framadate.css
Normal file
82
framadate/static/framadate/new_framadate.css
Normal file
|
@ -0,0 +1,82 @@
|
|||
.label-name {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2em;
|
||||
align-items: center;
|
||||
font-family: "Tomorrow", serif;
|
||||
|
||||
input {
|
||||
font-family: "Tomorrow", serif;
|
||||
font-size: 1em;
|
||||
padding: 0.5em;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
border: 1px solid #606060;
|
||||
}
|
||||
}
|
||||
|
||||
.dates {
|
||||
border: 1px solid #ccc;
|
||||
padding: 1em;
|
||||
margin: 1em 0;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
|
||||
.date {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2em;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="date"], input[type="time"] {
|
||||
font-family: "Tomorrow", serif;
|
||||
font-size: 1em;
|
||||
padding: 0.3em 0.7em;
|
||||
border: 1px solid #606060;
|
||||
border-radius: 10px;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-bottom: 2em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
border: none;
|
||||
background-color: #2973B2;
|
||||
color: white;
|
||||
padding: 0.4em 1em;
|
||||
font-size: 1em;
|
||||
font-family: "Tomorrow", serif;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
transition: box-shadow 0.2s ease;
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
.add-button:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.span-controls {
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.create {
|
||||
background-color: green;
|
||||
}
|
47
framadate/static/framadate/style.css
Normal file
47
framadate/static/framadate/style.css
Normal file
|
@ -0,0 +1,47 @@
|
|||
body {
|
||||
font-family: "Tomorrow", serif;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
margin: 1em;
|
||||
padding: 1em;
|
||||
border: 1px solid black;
|
||||
border-radius: 20px;
|
||||
font-size: 1.2em;
|
||||
|
||||
.framadates {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8em;
|
||||
|
||||
a {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
.new {
|
||||
transition: box-shadow 0.2s ease;
|
||||
background-color: #2973B2;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 0.5em 1em;
|
||||
margin: 1em 0;
|
||||
width: min-content;
|
||||
white-space: nowrap;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.new:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
98
framadate/templates/framadate/framadate_detail.html
Normal file
98
framadate/templates/framadate/framadate_detail.html
Normal file
|
@ -0,0 +1,98 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
{% load static %}
|
||||
<link rel="stylesheet" href="{% static 'framadate/style.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'framadate/framadate_detail.css' %}">
|
||||
<!-- fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Tomorrow:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
|
||||
<!-- icons -->
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=edit" />
|
||||
|
||||
<div class="framadate-detail">
|
||||
<a href="/framadate" class="back">Back</a>
|
||||
<h1>{{ framadate.name }} <a href="/framadate/{{ framadate.id }}/delete" class="delete-framadate">Delete</a></h1>
|
||||
|
||||
<form class="table" method="post" action="/framadate/{{ framadate.id }}/save-all">
|
||||
{% csrf_token %}
|
||||
<!-- hint -->
|
||||
<div class="row">
|
||||
<div class="item"></div>
|
||||
{% for mydate in framadate.days_set.all %}
|
||||
<div class="item">{{ mydate.day|date:"d.m." }} {% if mydate.time %} - {{ mydate.time|time:"H:00" }} {% endif %}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<!-- other users -->
|
||||
{% for name in framadate.names_set.all %}
|
||||
<div class="row">
|
||||
<div class="item item-with-edit">
|
||||
{{ name.name }}
|
||||
<a href="/framadate/{{ framadate.id }}/edit/{{ name.id }}"><span class="material-symbols-rounded">edit</span></a>
|
||||
</div>
|
||||
{% for record in name.records_set.all %}
|
||||
<div class="item {{ record.choiceName }}">{{ record.choiceName }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<!-- hint -->
|
||||
<div class="row mini-row">
|
||||
<div class="item"></div>
|
||||
{% for mydate in framadate.days_set.all %}
|
||||
<div class="item">{{ mydate.day|date:"d.m." }} {% if mydate.time %} - {{ mydate.time|time:"H:00" }} {% endif %}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<!-- inputs -->
|
||||
{% if edditing %}
|
||||
<div class="row">
|
||||
<div class="item center"><input type="text" class="item" name="name" id="name" value="{{ name.name }}"></div>
|
||||
{% for record in name.records_set.all %}
|
||||
<div class="item radio-column">
|
||||
<label><input type="radio" name="choice_{{ record.id }}" value="3" {% if record.choice == 3 %} checked {%endif%}>YES</label>
|
||||
<label><input type="radio" name="choice_{{ record.id }}" value="2" {% if record.choice == 2 %} checked {%endif%}>NO</label>
|
||||
<label><input type="radio" name="choice_{{ record.id }}" value="1" {% if record.choice == 1 %} checked {%endif%}>MAYBE</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row">
|
||||
<div class="item center"><input type="text" class="item" name="name" id="name"></div>
|
||||
{% for mydate in framadate.days_set.all %}
|
||||
<div class="item radio-column">
|
||||
<label><input type="radio" name="choice_{{ mydate.id }}" value="3">YES</label>
|
||||
<label><input type="radio" name="choice_{{ mydate.id }}" value="2">NO</label>
|
||||
<label><input type="radio" name="choice_{{ mydate.id }}" value="1">MAYBE</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- save handeling -->
|
||||
<div class="row">
|
||||
<input type="submit" class="submit" value="Save">
|
||||
{% if edditing %}
|
||||
<input name="delete" type="submit" class="submit delete" value="Delete" onclick="return confirmSubmit()">
|
||||
<input type="hidden" name="delete_id" value="{{ name.id }}">
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- best dates -->
|
||||
<h3>Best days - {{ max_count }}</h3>
|
||||
<div class="row">
|
||||
{% for mydate in best_dates %}
|
||||
<div class="item">{{ mydate.day|date:"d.m." }} {% if mydate.time %} - {{ mydate.time|time:"H:00" }} {% endif %}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function confirmSubmit() {
|
||||
return confirm("Are you sure you want delete the records?");
|
||||
}
|
||||
|
||||
// prevent .delete-framadate click event
|
||||
document.querySelector(".delete-framadate").addEventListener("click", function(event) {
|
||||
if (!confirm("Are you sure you want delete the framadate?")) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
</script>
|
31
framadate/templates/framadate/framadate_list.html
Normal file
31
framadate/templates/framadate/framadate_list.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
{% load static %}
|
||||
<link rel="stylesheet" href="{% static 'framadate/style.css' %}">
|
||||
<!-- fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Tomorrow:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
|
||||
|
||||
<div class="column">
|
||||
<h1>All current framadates</h1>
|
||||
<div class="framadates">
|
||||
{% for framadate in object_list %}
|
||||
{% if framadate.isRelevant %}
|
||||
<a href="framadate/{{ framadate.id }}">{{ framadate.name }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<a href="/framadate/new" class="new">Create new</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1>Past framadates</h1>
|
||||
<div class="framadates">
|
||||
{% for framadate in object_list %}
|
||||
{% if not framadate.isRelevant %}
|
||||
<a href="framadate/{{ framadate.id }}">{{ framadate.name }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
82
framadate/templates/framadate/new_framadate.html
Normal file
82
framadate/templates/framadate/new_framadate.html
Normal file
|
@ -0,0 +1,82 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
{% load static %}
|
||||
<link rel="stylesheet" href="{% static 'framadate/style.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'framadate/new_framadate.css' %}">
|
||||
<!-- fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Tomorrow:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
|
||||
|
||||
<a href="/framadate" class="back">Back</a>
|
||||
<h1>New framadate</h1>
|
||||
|
||||
<form action="/framadate/new" method="post">
|
||||
{% csrf_token %}
|
||||
<label class="label-name">name: <input type="text" required name="name"></label>
|
||||
|
||||
<div class="dates">
|
||||
<!-- <div id="date_1" class="date">
|
||||
<label>date: <input type="date" name="" id="" required></label>
|
||||
<label>time (optional, if empty -> entire day ): <input type="time" name="" id="" step="3600000"></label>
|
||||
<button type="button" onclick="removeDate(1)">remove</button>
|
||||
</div> -->
|
||||
</div>
|
||||
<!-- controls -->
|
||||
<div class="controls">
|
||||
<button type="button" class="add-button" onclick="addDate()">Add date</button>
|
||||
<div class="span-controls">
|
||||
<label>from: <input id="from" type="date"></label>
|
||||
<label>to: <input id="to" type="date"></label>
|
||||
<button type="button" onclick="addSpan()" class="add-button">Add span</button>
|
||||
</div>
|
||||
</div>
|
||||
<input type="submit" value="Create" class="add-button create">
|
||||
</form>
|
||||
|
||||
|
||||
<script>
|
||||
numberOfDates = 1;
|
||||
|
||||
function addDate() {
|
||||
let newDate = document.createElement("div");
|
||||
newDate.classList.add("date");
|
||||
numberOfDates++;
|
||||
newDate.id = "date_" + numberOfDates;
|
||||
// step does not work in safari...
|
||||
newDate.innerHTML = `
|
||||
<label>date: <input type="date" name="date" required></label>
|
||||
<label>time (optional, if empty -> entire day ): <input type="time" name="time" step="3600"></label>
|
||||
<button type="button" onclick="removeDate(`+numberOfDates+`)">remove</button>
|
||||
`;
|
||||
dates = document.querySelector(".dates");
|
||||
dates.appendChild(newDate);
|
||||
}
|
||||
|
||||
function removeDate(id_to_remove) {
|
||||
document.getElementById("date_" + id_to_remove).remove();
|
||||
}
|
||||
|
||||
let from = document.getElementById("from");
|
||||
let to = document.getElementById("to");
|
||||
function addSpan() {
|
||||
let fromDate = new Date(from.value);
|
||||
let toDate = new Date(to.value);
|
||||
// print date of every date in range
|
||||
while (fromDate <= toDate) {
|
||||
let newDate = document.createElement("div");
|
||||
newDate.classList.add("date");
|
||||
numberOfDates++;
|
||||
newDate.id = "date_" + numberOfDates;
|
||||
newDate.innerHTML = `
|
||||
<label>date: <input type="date" name="date" required value="`+fromDate.toISOString().split("T")[0]+`"></label>
|
||||
<label>time (optional, if empty -> entire day ): <input type="time" name="time" step="3600"></label>
|
||||
<button type="button" onclick="removeDate(`+numberOfDates+`)">remove</button>
|
||||
`;
|
||||
dates = document.querySelector(".dates");
|
||||
dates.appendChild(newDate);
|
||||
fromDate.setDate(fromDate.getDate() + 1);
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
3
framadate/tests.py
Normal file
3
framadate/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
11
framadate/urls.py
Normal file
11
framadate/urls.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("framadate", views.FramadateListView.as_view(), name="framadate_list"),
|
||||
path("framadate/new", views.NewFramadate.as_view(), name="new_framadate"),
|
||||
path("framadate/<int:pk>", views.FramadateDetail.as_view(), name="framadate_detail"),
|
||||
path("framadate/<int:pk>/edit/<int:name_id>", views.FramadateDetailEdit.as_view(), name="framadate_detail_edit"),
|
||||
path("framadate/<int:pk>/save-all", views.save_all, name="save_all"),
|
||||
path("framadate/<int:pk>/delete", views.delete_framadate, name="delete"),
|
||||
]
|
93
framadate/views.py
Normal file
93
framadate/views.py
Normal file
|
@ -0,0 +1,93 @@
|
|||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.urls import reverse
|
||||
from django.views.generic.list import ListView
|
||||
from django.views.generic import TemplateView
|
||||
from .models import *
|
||||
|
||||
class FramadateListView(ListView):
|
||||
model = Framadate
|
||||
template_name = 'framadate/framadate_list.html'
|
||||
|
||||
class NewFramadate(TemplateView):
|
||||
template_name = 'framadate/new_framadate.html'
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
new_framadate = Framadate.objects.create(name=request.POST['name'])
|
||||
dates = request.POST.getlist('date')
|
||||
times = request.POST.getlist('time')
|
||||
for i in range(len(dates)):
|
||||
FramadateDate.objects.create(framadate=new_framadate, day=dates[i], time=(times[i] if times[i] else None))
|
||||
return HttpResponseRedirect("/framadate/" + str(new_framadate.id))
|
||||
|
||||
class FramadateDetail(TemplateView):
|
||||
template_name = 'framadate/framadate_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
addContextForFramadate(context, **kwargs)
|
||||
return context
|
||||
|
||||
class FramadateDetailEdit(TemplateView):
|
||||
template_name = 'framadate/framadate_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
addContextForFramadate(context, **kwargs)
|
||||
# data to edit
|
||||
context["edditing"] = True
|
||||
context["name"] = NameInFramadate.objects.get(pk=kwargs['name_id'])
|
||||
return context
|
||||
|
||||
def save_all(request, pk):
|
||||
if request.method != "POST":
|
||||
return HttpResponse("Not a POST request")
|
||||
if request.POST.get("name") == "":
|
||||
return HttpResponseRedirect(reverse('framadate_detail', args=[pk]))
|
||||
framadate = Framadate.objects.get(pk=pk)
|
||||
# deleting
|
||||
if request.POST.get("delete"):
|
||||
try:
|
||||
NameInFramadate.objects.get(pk=request.POST['delete_id']).delete()
|
||||
except:
|
||||
return HttpResponse("Ups something went wrong, can't delete")
|
||||
return HttpResponseRedirect(reverse('framadate_detail', args=[pk]))
|
||||
# new
|
||||
if not request.POST.get("delete_id"):
|
||||
new_name = NameInFramadate.objects.create(framadate=framadate, name=request.POST['name'])
|
||||
for day in framadate.days_set.all():
|
||||
Record.objects.create(nameInFramadate=new_name, framadateDate=day, choice=(request.POST["choice_" + str(day.id)] if request.POST.get("choice_" + str(day.id)) else 0))
|
||||
else: # edditing
|
||||
name_id = request.POST['delete_id']
|
||||
name = NameInFramadate.objects.get(pk=name_id)
|
||||
name.name = request.POST['name']
|
||||
name.save()
|
||||
for record in name.records_set.all():
|
||||
|
||||
record.choice = (request.POST["choice_" + str(record.id)] if request.POST.get("choice_" + str(record.id)) else 0)
|
||||
record.save()
|
||||
return HttpResponseRedirect(reverse('framadate_detail', args=[pk]))
|
||||
|
||||
def delete_framadate(request, pk):
|
||||
framadate = Framadate.objects.get(pk=pk)
|
||||
framadate.delete()
|
||||
return HttpResponseRedirect("/framadate")
|
||||
|
||||
def addContextForFramadate(context, **kwargs):
|
||||
context['framadate'] = Framadate.objects.get(pk=kwargs['pk'])
|
||||
context['choices'] = Choice.choices
|
||||
context['best_dates'] = []
|
||||
best_dates = []
|
||||
best = -1
|
||||
for day in context['framadate'].days_set.all(): # kinda slow?
|
||||
count = 0
|
||||
for record in day.records_set.all():
|
||||
if record.choice == 3:
|
||||
count += 1
|
||||
if count > best:
|
||||
best_dates = [day]
|
||||
best = count
|
||||
elif count == best:
|
||||
best_dates.append(day)
|
||||
context['best_dates'] = best_dates
|
||||
context['max_count'] = best
|
||||
|
|
@ -148,6 +148,7 @@ INSTALLED_APPS = (
|
|||
'vyroci',
|
||||
'sifrovacka',
|
||||
'novinky',
|
||||
'framadate',
|
||||
|
||||
# Admin upravy:
|
||||
|
||||
|
|
|
@ -62,6 +62,9 @@ urlpatterns = [
|
|||
|
||||
# Miniapka na šifrovačku
|
||||
path('sifrovacka/', include('sifrovacka.urls')),
|
||||
|
||||
# Framadate
|
||||
path('', include('framadate.urls')),
|
||||
]
|
||||
|
||||
# This is only needed when using runserver.
|
||||
|
|
|
@ -13,3 +13,4 @@ from soustredeni.models import *
|
|||
from treenode.models import *
|
||||
from tvorba.models import *
|
||||
from various.models import *
|
||||
from framadate.models import *
|
||||
|
|
Loading…
Reference in a new issue