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', | 	'vyroci', | ||||||
| 	'sifrovacka', | 	'sifrovacka', | ||||||
| 	'novinky', | 	'novinky', | ||||||
|  | 	'framadate', | ||||||
| 
 | 
 | ||||||
| 	# Admin upravy: | 	# Admin upravy: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -62,6 +62,9 @@ urlpatterns = [ | ||||||
| 
 | 
 | ||||||
| 	# Miniapka na šifrovačku | 	# Miniapka na šifrovačku | ||||||
| 	path('sifrovacka/', include('sifrovacka.urls')), | 	path('sifrovacka/', include('sifrovacka.urls')), | ||||||
|  | 
 | ||||||
|  | 	# Framadate | ||||||
|  | 	path('', include('framadate.urls')), | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| # This is only needed when using runserver. | # This is only needed when using runserver. | ||||||
|  |  | ||||||
|  | @ -13,3 +13,4 @@ from soustredeni.models import * | ||||||
| from treenode.models import * | from treenode.models import * | ||||||
| from tvorba.models import * | from tvorba.models import * | ||||||
| from various.models import * | from various.models import * | ||||||
|  | from framadate.models import * | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue