From 479c9eb192e6309e1919f5cada1ec1bc9992af05 Mon Sep 17 00:00:00 2001 From: ticvac Date: Sun, 26 Jan 2025 18:21:30 +0100 Subject: [PATCH] framadate celkem fungujici, nesplnuje design zbytku webu... --- framadate/__init__.py | 0 framadate/admin.py | 3 + framadate/apps.py | 6 + framadate/migrations/0001_initial.py | 54 +++++++ framadate/migrations/__init__.py | 0 framadate/models.py | 59 ++++++++ .../static/framadate/framadate_detail.css | 132 ++++++++++++++++++ framadate/static/framadate/new_framadate.css | 82 +++++++++++ framadate/static/framadate/style.css | 47 +++++++ .../templates/framadate/framadate_detail.html | 98 +++++++++++++ .../templates/framadate/framadate_list.html | 31 ++++ .../templates/framadate/new_framadate.html | 82 +++++++++++ framadate/tests.py | 3 + framadate/urls.py | 11 ++ framadate/views.py | 93 ++++++++++++ mamweb/settings_common.py | 1 + mamweb/urls.py | 3 + mamweb/vsechno.py | 1 + 18 files changed, 706 insertions(+) create mode 100644 framadate/__init__.py create mode 100644 framadate/admin.py create mode 100644 framadate/apps.py create mode 100644 framadate/migrations/0001_initial.py create mode 100644 framadate/migrations/__init__.py create mode 100644 framadate/models.py create mode 100644 framadate/static/framadate/framadate_detail.css create mode 100644 framadate/static/framadate/new_framadate.css create mode 100644 framadate/static/framadate/style.css create mode 100644 framadate/templates/framadate/framadate_detail.html create mode 100644 framadate/templates/framadate/framadate_list.html create mode 100644 framadate/templates/framadate/new_framadate.html create mode 100644 framadate/tests.py create mode 100644 framadate/urls.py create mode 100644 framadate/views.py diff --git a/framadate/__init__.py b/framadate/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/framadate/admin.py b/framadate/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/framadate/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/framadate/apps.py b/framadate/apps.py new file mode 100644 index 00000000..027fcb08 --- /dev/null +++ b/framadate/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class FramadateConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'framadate' diff --git a/framadate/migrations/0001_initial.py b/framadate/migrations/0001_initial.py new file mode 100644 index 00000000..ce443dc2 --- /dev/null +++ b/framadate/migrations/0001_initial.py @@ -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'], + }, + ), + ] diff --git a/framadate/migrations/__init__.py b/framadate/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/framadate/models.py b/framadate/models.py new file mode 100644 index 00000000..81ea15e5 --- /dev/null +++ b/framadate/models.py @@ -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'] diff --git a/framadate/static/framadate/framadate_detail.css b/framadate/static/framadate/framadate_detail.css new file mode 100644 index 00000000..6afe7b30 --- /dev/null +++ b/framadate/static/framadate/framadate_detail.css @@ -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; +} \ No newline at end of file diff --git a/framadate/static/framadate/new_framadate.css b/framadate/static/framadate/new_framadate.css new file mode 100644 index 00000000..6b43a30e --- /dev/null +++ b/framadate/static/framadate/new_framadate.css @@ -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; +} \ No newline at end of file diff --git a/framadate/static/framadate/style.css b/framadate/static/framadate/style.css new file mode 100644 index 00000000..fe89301b --- /dev/null +++ b/framadate/static/framadate/style.css @@ -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; + } +} diff --git a/framadate/templates/framadate/framadate_detail.html b/framadate/templates/framadate/framadate_detail.html new file mode 100644 index 00000000..8fde59bf --- /dev/null +++ b/framadate/templates/framadate/framadate_detail.html @@ -0,0 +1,98 @@ + + +{% load static %} + + + + + + + + + +
+ Back +

{{ framadate.name }} Delete

+ +
+ {% csrf_token %} + +
+
+ {% for mydate in framadate.days_set.all %} +
{{ mydate.day|date:"d.m." }} {% if mydate.time %} - {{ mydate.time|time:"H:00" }} {% endif %}
+ {% endfor %} +
+ + {% for name in framadate.names_set.all %} +
+
+ {{ name.name }} + edit +
+ {% for record in name.records_set.all %} +
{{ record.choiceName }}
+ {% endfor %} +
+ {% endfor %} + +
+
+ {% for mydate in framadate.days_set.all %} +
{{ mydate.day|date:"d.m." }} {% if mydate.time %} - {{ mydate.time|time:"H:00" }} {% endif %}
+ {% endfor %} +
+ + {% if edditing %} +
+
+ {% for record in name.records_set.all %} +
+ + + +
+ {% endfor %} +
+ {% else %} +
+
+ {% for mydate in framadate.days_set.all %} +
+ + + +
+ {% endfor %} +
+ {% endif %} + +
+ + {% if edditing %} + + + {% endif %} +
+ +

Best days - {{ max_count }}

+
+ {% for mydate in best_dates %} +
{{ mydate.day|date:"d.m." }} {% if mydate.time %} - {{ mydate.time|time:"H:00" }} {% endif %}
+ {% endfor %} +
+
+
+ + diff --git a/framadate/templates/framadate/framadate_list.html b/framadate/templates/framadate/framadate_list.html new file mode 100644 index 00000000..2d01aab5 --- /dev/null +++ b/framadate/templates/framadate/framadate_list.html @@ -0,0 +1,31 @@ + + +{% load static %} + + + + + + +
+

All current framadates

+
+ {% for framadate in object_list %} + {% if framadate.isRelevant %} + {{ framadate.name }} + {% endif %} + {% endfor %} +
+ Create new +
+ +
+

Past framadates

+
+ {% for framadate in object_list %} + {% if not framadate.isRelevant %} + {{ framadate.name }} + {% endif %} + {% endfor %} +
+
diff --git a/framadate/templates/framadate/new_framadate.html b/framadate/templates/framadate/new_framadate.html new file mode 100644 index 00000000..bc906b9e --- /dev/null +++ b/framadate/templates/framadate/new_framadate.html @@ -0,0 +1,82 @@ + + +{% load static %} + + + + + + + +Back +

New framadate

+ +
+ {% csrf_token %} + + +
+ +
+ +
+ +
+ + + +
+
+ +
+ + + diff --git a/framadate/tests.py b/framadate/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/framadate/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/framadate/urls.py b/framadate/urls.py new file mode 100644 index 00000000..6556ef98 --- /dev/null +++ b/framadate/urls.py @@ -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/", views.FramadateDetail.as_view(), name="framadate_detail"), + path("framadate//edit/", views.FramadateDetailEdit.as_view(), name="framadate_detail_edit"), + path("framadate//save-all", views.save_all, name="save_all"), + path("framadate//delete", views.delete_framadate, name="delete"), +] diff --git a/framadate/views.py b/framadate/views.py new file mode 100644 index 00000000..33ede333 --- /dev/null +++ b/framadate/views.py @@ -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 + \ No newline at end of file diff --git a/mamweb/settings_common.py b/mamweb/settings_common.py index 3bfbcfc4..376e8873 100644 --- a/mamweb/settings_common.py +++ b/mamweb/settings_common.py @@ -148,6 +148,7 @@ INSTALLED_APPS = ( 'vyroci', 'sifrovacka', 'novinky', + 'framadate', # Admin upravy: diff --git a/mamweb/urls.py b/mamweb/urls.py index eed460c2..fbf40e99 100644 --- a/mamweb/urls.py +++ b/mamweb/urls.py @@ -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. diff --git a/mamweb/vsechno.py b/mamweb/vsechno.py index 9130ebc2..3020bd25 100644 --- a/mamweb/vsechno.py +++ b/mamweb/vsechno.py @@ -13,3 +13,4 @@ from soustredeni.models import * from treenode.models import * from tvorba.models import * from various.models import * +from framadate.models import *