From 3f4dc5f8be3285eae7fbcae28b0b5541c7210a19 Mon Sep 17 00:00:00 2001 From: Klaus-Uwe Mitterer Date: Fri, 1 Jan 2021 17:49:39 +0100 Subject: [PATCH] Dreams module completed (?) --- TODO.md | 16 +- dreams/forms.py | 17 ++ dreams/models.py | 12 + dreams/templates/dreams/dream_delete.html | 16 ++ dreams/templates/dreams/dream_edit.html | 60 ++++ dreams/templates/dreams/dream_list.html | 34 +++ dreams/templates/dreams/dream_view.html | 70 +++++ dreams/templates/dreams/mood_list.html | 28 ++ .../templates/dreams/notification_delete.html | 16 ++ .../templates/dreams/notification_edit.html | 30 ++ .../templates/dreams/notification_list.html | 28 ++ dreams/templates/dreams/theme_delete.html | 16 ++ dreams/templates/dreams/theme_edit.html | 30 ++ dreams/templates/dreams/theme_list.html | 28 ++ dreams/urls.py | 21 ++ dreams/views.py | 258 +++++++++++++++++- frontend/templates/frontend/sidebar.html | 29 ++ kumify/urls.py | 1 + 18 files changed, 705 insertions(+), 5 deletions(-) create mode 100644 dreams/forms.py create mode 100644 dreams/templates/dreams/dream_delete.html create mode 100644 dreams/templates/dreams/dream_edit.html create mode 100644 dreams/templates/dreams/dream_list.html create mode 100644 dreams/templates/dreams/dream_view.html create mode 100644 dreams/templates/dreams/mood_list.html create mode 100644 dreams/templates/dreams/notification_delete.html create mode 100644 dreams/templates/dreams/notification_edit.html create mode 100644 dreams/templates/dreams/notification_list.html create mode 100644 dreams/templates/dreams/theme_delete.html create mode 100644 dreams/templates/dreams/theme_edit.html create mode 100644 dreams/templates/dreams/theme_list.html create mode 100644 dreams/urls.py diff --git a/TODO.md b/TODO.md index ed41ffb..577c397 100644 --- a/TODO.md +++ b/TODO.md @@ -2,6 +2,7 @@ ## mood module +[_] Missing mood views [ ] Statistics / graphs ## cbt module @@ -16,7 +17,14 @@ ## dreams module -[ ] New dream page -[ ] Edit dream page -[ ] Delete dream page -[ ] Statistics / template tags \ No newline at end of file +[x] New dream page +[x] Edit dream page +[x] Delete dream page +[ ] Statistics / template tags +[ ] Check for copy-paste errors in pages + +## Frontend + +[ ] Better icons for sidebar items +[ ] Sidebar auto-generation (?) +[ ] Fix sidebar highlighting for pages with same name \ No newline at end of file diff --git a/dreams/forms.py b/dreams/forms.py new file mode 100644 index 0000000..86ad0e4 --- /dev/null +++ b/dreams/forms.py @@ -0,0 +1,17 @@ +from django.forms import ModelForm, ModelMultipleChoiceField + +from multiupload.fields import MultiFileField + +from .models import Dream, Theme + +class DreamForm(ModelForm): + uploads = MultiFileField(required=False) + themes = ModelMultipleChoiceField(queryset=Theme.objects.all()) + + class Meta: + model = Dream + fields = ["timestamp", "mood", "title", "content", 'type', 'wet', 'lucid'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["mood"].required = False \ No newline at end of file diff --git a/dreams/models.py b/dreams/models.py index 5d54a6c..52f1d1f 100644 --- a/dreams/models.py +++ b/dreams/models.py @@ -14,6 +14,9 @@ class Theme(models.Model): icon = models.CharField(default="fas fa-bed", max_length=64) color = ColorField(default="#000000") + def __str__(self): + return self.name + class Dream(models.Model): class DreamTypes(models.IntegerChoices): NIGHT = 0, 'Night (main) sleep' @@ -21,6 +24,7 @@ class Dream(models.Model): NAP = 2, 'Napping' user = models.ForeignKey(get_user_model(), models.CASCADE) + timestamp = models.DateTimeField(default=timezone.now) title = models.CharField(max_length=64) content = models.TextField() type = models.IntegerField(choices=DreamTypes.choices) @@ -28,6 +32,14 @@ class Dream(models.Model): lucid = models.BooleanField(default=False) wet = models.BooleanField(default=False) + @property + def short_text(self): + return self.title or self.content[:64] + + @property + def theme_set(self): + return [theme.theme for theme in self.dreamtheme_set.all()] + class DreamTheme(models.Model): dream = models.ForeignKey(Dream, models.CASCADE) theme = models.ForeignKey(Theme, models.CASCADE) diff --git a/dreams/templates/dreams/dream_delete.html b/dreams/templates/dreams/dream_delete.html new file mode 100644 index 0000000..17b28be --- /dev/null +++ b/dreams/templates/dreams/dream_delete.html @@ -0,0 +1,16 @@ +{% extends "frontend/base.html" %} +{% block "content" %} + +
+
+
Delete {{ object }}
+
+
+
+

Are you sure you wish to delete this dream? This cannot be undone.

+
{% csrf_token %}
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/dreams/templates/dreams/dream_edit.html b/dreams/templates/dreams/dream_edit.html new file mode 100644 index 0000000..1e127ff --- /dev/null +++ b/dreams/templates/dreams/dream_edit.html @@ -0,0 +1,60 @@ +{% extends "frontend/base.html" %} +{% block "content" %} + +{% if form.errors %} +{% for field in form %} +{% for error in field.errors %} + +{% endfor %} +{% endfor %} +{% endif %} +
+
+
Edit {{ object.title }}
+
+
+
+ {% csrf_token %} +
+ + + + + + + + + + + + + + + + +
Lucid Dream?
Wet Dream?
+ {% for theme in request.user.theme_set.all %} + {{ theme }}
+ {% endfor %} +
+
+ +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/dreams/templates/dreams/dream_list.html b/dreams/templates/dreams/dream_list.html new file mode 100644 index 0000000..657c79e --- /dev/null +++ b/dreams/templates/dreams/dream_list.html @@ -0,0 +1,34 @@ +{% extends "frontend/base.html" %} +{% block "content" %} + +
+
+
Your Dreams
+
+
+
+ + + + + + + + + + + {% for dream in object_list %} + + + + + + + {% endfor %} + +
TimestampMoodThemesTitle
{{ dream.timestamp }} {{ dream.mood }}{% for theme in dream.dreamtheme_set.all %} {% endfor %}{{ dream.short_text }}
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/dreams/templates/dreams/dream_view.html b/dreams/templates/dreams/dream_view.html new file mode 100644 index 0000000..445096e --- /dev/null +++ b/dreams/templates/dreams/dream_view.html @@ -0,0 +1,70 @@ +{% extends "frontend/base.html" %} +{% block "content" %} + +
+ + +
+
+ +
+
Dream Entry
+ +
+ +
+

{{ object.timestamp }}

+

{{ object.title }}

+
{{ object.content|linebreaks }}
+
+
+
+ + +
+
+ +
+
Mood
+
+ +
+ +
+
+
+
+ +
+
+
Themes
+
+
+
+
+ {% for theme in object.dreamtheme_set.all %} +
+ +
+ {% endfor %} +
+
+
+ +
+
+
Attachments
+
+
+
+ +
+
+ +
+ +{% endblock %} \ No newline at end of file diff --git a/dreams/templates/dreams/mood_list.html b/dreams/templates/dreams/mood_list.html new file mode 100644 index 0000000..2ec727a --- /dev/null +++ b/dreams/templates/dreams/mood_list.html @@ -0,0 +1,28 @@ +{% extends "frontend/base.html" %} +{% block "content" %} + +
+
+
Your Moods
+
+
+
+ + + + + + + + {% for mood in object_list %} + + + + {% endfor %} + +
Name (Value)
{{ mood }} ({{ mood.value }})
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/dreams/templates/dreams/notification_delete.html b/dreams/templates/dreams/notification_delete.html new file mode 100644 index 0000000..2d8ab17 --- /dev/null +++ b/dreams/templates/dreams/notification_delete.html @@ -0,0 +1,16 @@ +{% extends "frontend/base.html" %} +{% block "content" %} + +
+
+
Delete {{ object.time }} Notification?
+
+
+
+

Are you sure you wish to delete this notification? This cannot be undone.

+
{% csrf_token %}
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/dreams/templates/dreams/notification_edit.html b/dreams/templates/dreams/notification_edit.html new file mode 100644 index 0000000..5f4566e --- /dev/null +++ b/dreams/templates/dreams/notification_edit.html @@ -0,0 +1,30 @@ +{% extends "frontend/base.html" %} +{% block "content" %} + +{% if form.errors %} +{% for field in form %} +{% for error in field.errors %} + +{% endfor %} +{% endfor %} +{% endif %} +
+
+
Edit {{ object.time }}
+
+
+
+ {% csrf_token %} +
+ + +
+
+ +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/dreams/templates/dreams/notification_list.html b/dreams/templates/dreams/notification_list.html new file mode 100644 index 0000000..5a82619 --- /dev/null +++ b/dreams/templates/dreams/notification_list.html @@ -0,0 +1,28 @@ +{% extends "frontend/base.html" %} +{% block "content" %} + +
+
+
Your Daily Notifications
+
+
+
+ + + + + + + + {% for notification in object_list %} + + + + {% endfor %} + +
Time
{{ notification.time }}
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/dreams/templates/dreams/theme_delete.html b/dreams/templates/dreams/theme_delete.html new file mode 100644 index 0000000..d714baf --- /dev/null +++ b/dreams/templates/dreams/theme_delete.html @@ -0,0 +1,16 @@ +{% extends "frontend/base.html" %} +{% block "content" %} + +
+
+
Delete {{ object }}
+
+
+
+

Are you sure you wish to delete {{ object }}? This cannot be undone. Mood entries that are associated with this activity will not be deleted.

+
{% csrf_token %}
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/dreams/templates/dreams/theme_edit.html b/dreams/templates/dreams/theme_edit.html new file mode 100644 index 0000000..3256677 --- /dev/null +++ b/dreams/templates/dreams/theme_edit.html @@ -0,0 +1,30 @@ +{% extends "frontend/base.html" %} +{% block "content" %} + +
+
+
Edit {{ object.name }}
+
+
+
+ {% csrf_token %} +
+ + + + +
+
+ +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/dreams/templates/dreams/theme_list.html b/dreams/templates/dreams/theme_list.html new file mode 100644 index 0000000..6946444 --- /dev/null +++ b/dreams/templates/dreams/theme_list.html @@ -0,0 +1,28 @@ +{% extends "frontend/base.html" %} +{% block "content" %} + +
+
+
Your Themes
+
+
+
+ + + + + + + + {% for theme in object_list %} + + + + {% endfor %} + +
Name
{{ theme }}
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/dreams/urls.py b/dreams/urls.py new file mode 100644 index 0000000..0375aab --- /dev/null +++ b/dreams/urls.py @@ -0,0 +1,21 @@ +from .views import DreamListView, DreamViewView, DreamDeleteView, DreamEditView, DreamCreateView, ThemeListView, ThemeEditView, ThemeCreateView, ThemeDeleteView, NotificationCreateView, NotificationDeleteView, NotificationEditView, NotificationListView + +from django.urls import path, include + +app_name = "dreams" + +urlpatterns = [ + path('', DreamListView.as_view(), name="dream_list"), + path('dream//view/', DreamViewView.as_view(), name="dream_view"), + path('dream//edit/', DreamEditView.as_view(), name="dream_edit"), + path('dream//delete/', DreamDeleteView.as_view(), name="dream_delete"), + path('dream/new/', DreamCreateView.as_view(), name="dream_create"), + path('theme/', ThemeListView.as_view(), name="theme_list"), + path('theme//edit/', ThemeEditView.as_view(), name="theme_edit"), + path('theme/new/', ThemeCreateView.as_view(), name="theme_create"), + path('theme//delete/', ThemeDeleteView.as_view(), name="theme_delete"), + path('notification/', NotificationListView.as_view(), name="notification_list"), + path('notification//edit/', NotificationEditView.as_view(), name="notification_edit"), + path('notification//delete/', NotificationDeleteView.as_view(), name="notification_delete"), + path('notification/new/', NotificationCreateView.as_view(), name="notification_create"), +] \ No newline at end of file diff --git a/dreams/views.py b/dreams/views.py index 91ea44a..01db574 100644 --- a/dreams/views.py +++ b/dreams/views.py @@ -1,3 +1,259 @@ from django.shortcuts import render -# Create your views here. +from django.views.generic import TemplateView, ListView, UpdateView, DetailView, CreateView, DeleteView +from django.contrib.auth.mixins import LoginRequiredMixin +from django.shortcuts import get_object_or_404 +from django.urls import reverse_lazy +from django.http import HttpResponseRedirect + +from .models import Dream, DreamTheme, DreamMedia, Theme +from .forms import DreamForm + +from msgio.models import NotificationDailySchedule, Notification + +class DreamListView(LoginRequiredMixin, ListView): + template_name = "dreams/dream_list.html" + model = Dream + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["title"] = "Dream List" + context["subtitle"] = "A list of the dreams you have entered so far." + context["buttons"] = [(reverse_lazy("dreams:dream_create"), "New Dream", "plus")] + return context + + def get_queryset(self): + return Dream.objects.filter(user=self.request.user).order_by('timestamp') + + +class DreamViewView(LoginRequiredMixin, DetailView): + template_name = "dreams/dream_view.html" + model = Dream + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["title"] = "View Dream" + context["subtitle"] = "View the details of your dream." + context["buttons"] = [(reverse_lazy("dreams:dream_edit", kwargs={"id": self.kwargs["id"]}), "Edit Dream", "pen")] + return context + + def get_object(self): + return get_object_or_404(Dream, user=self.request.user, id=self.kwargs["id"]) + + +class DreamCreateView(LoginRequiredMixin, CreateView): + template_name = "dreams/dream_edit.html" + form_class = DreamForm + model = Dream + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["title"] = "Create Dream" + context["subtitle"] = "What did you dream?" + context["scripts"] = ["frontend/js/dropdown-to-buttons.js"] + return context + + def form_valid(self, form): + form.instance.user = self.request.user + + ret = super().form_valid(form) + + for theme in form.cleaned_data["themes"]: + if theme.user == self.request.user: + DreamTheme.objects.create(theme=theme, dream=form.instance) + + for attachment in form.cleaned_data["uploads"]: + DreamMedia.objects.create(dream=form.instance, media=attachment) + + return ret + + def get_success_url(self): + return reverse_lazy("dreams:dream_view", kwargs={"id": self.object.id}) + + +class DreamEditView(LoginRequiredMixin, UpdateView): + template_name = "dreams/dream_edit.html" + form_class = DreamForm + model = Dream + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["title"] = "Update Dream" + context["subtitle"] = "Change details of a dream you entered before." + context["scripts"] = ["frontend/js/dropdown-to-buttons.js"] + context["buttons"] = [(reverse_lazy("dreams:dream_delete", kwargs={"id": self.kwargs["id"]}), "Delete Dream", "trash-alt")] + return context + + def get_object(self): + return get_object_or_404(Dream, user=self.request.user, id=self.kwargs["id"]) + + def form_valid(self, form): + for theme in form.cleaned_data["themes"]: + if theme.user == self.request.user: + if not theme in form.instance.theme_set: + DreamTheme.objects.create(theme=theme, dream=form.instance) + + for dreamtheme in form.instance.dreamtheme_set.all(): + if not dreamtheme.theme in form.cleaned_data["themes"]: + dreamtheme.delete() + + for upload in form.cleaned_data["uploads"]: + DreamMedia.objects.create(dream=form.instance, media=upload) + + return super().form_valid(form) + + def get_success_url(self): + return reverse_lazy("dreams:dream_view", kwargs={"id": self.object.id}) + + +class DreamDeleteView(LoginRequiredMixin, DeleteView): + template_name = "dreams/dream_delete.html" + model = Dream + + def get_object(self): + return get_object_or_404(Dream, user=self.request.user, id=self.kwargs["id"]) + + def get_success_url(self): + return reverse_lazy("dreams:dream_list") + + +class ThemeListView(LoginRequiredMixin, ListView): + template_name = "dreams/theme_list.html" + model = Theme + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["title"] = "Themes" + context["subtitle"] = "The themes you have defined for your dreams." + context["buttons"] = [(reverse_lazy("dreams:theme_create"), "Create Theme", "pen")] + return context + + def get_queryset(self): + return Theme.objects.filter(user=self.request.user) + + +class ThemeEditView(LoginRequiredMixin, UpdateView): + template_name = "dreams/theme_edit.html" + model = Theme + fields = ["name", "icon", "color"] + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["title"] = "Edit Theme" + context["subtitle"] = "Make changes to the theme." + context["scripts"] = ["colorfield/jscolor/jscolor.js", "colorfield/colorfield.js", "frontend/js/fontawesome-iconpicker.min.js", "frontend/js/iconpicker-loader.js"] + context["styles"] = ["frontend/css/fontawesome-iconpicker.min.css"] + context["buttons"] = [(reverse_lazy("dreams:theme_delete", kwargs={"id": self.kwargs["id"]}), "Delete Theme", "trash-alt")] + return context + + def get_object(self): + return get_object_or_404(Theme, user=self.request.user, id=self.kwargs["id"]) + + def get_success_url(self): + return reverse_lazy("dreams:theme_list") + + +class ThemeCreateView(LoginRequiredMixin, CreateView): + template_name = "dreams/theme_edit.html" + model = Theme + fields = ["name", "icon", "color"] + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["title"] = "Create Theme" + context["subtitle"] = "Add a new theme for your dreams." + context["scripts"] = ["colorfield/jscolor/jscolor.js", "colorfield/colorfield.js", "frontend/js/fontawesome-iconpicker.min.js", "frontend/js/iconpicker-loader.js"] + context["styles"] = ["frontend/css/fontawesome-iconpicker.min.css"] + return context + + def form_valid(self, form): + obj = form.save(commit=False) + obj.user = self.request.user + return super().form_valid(form) + + def get_success_url(self): + return reverse_lazy("dreams:theme_list") + + +class ThemeDeleteView(LoginRequiredMixin, DeleteView): + template_name = "dreams/theme_delete.html" + model = Theme + + def get_object(self): + return get_object_or_404(Theme, user=self.request.user, id=self.kwargs["id"]) + + def get_success_url(self): + return reverse_lazy("dreams:theme_list") + + +class NotificationListView(LoginRequiredMixin, ListView): + template_name = "dreams/notification_list.html" + model = NotificationDailySchedule + fields = ["time"] + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["title"] = "Notifications" + context["subtitle"] = "The daily reminders you have set up." + context["buttons"] = [(reverse_lazy("dreams:notification_create"), "New Notification", "plus")] + return context + + def get_queryset(self): + return NotificationDailySchedule.objects.filter(notification__recipient=self.request.user, notification__app="dreams") + + +class NotificationCreateView(LoginRequiredMixin, CreateView): + template_name = "dreams/notification_edit.html" + model = NotificationDailySchedule + fields = ["time"] + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["title"] = "Create Notification" + context["subtitle"] = "Add a new daily notification." + return context + + def form_valid(self, form): + notification = Notification.objects.create(content="What did you dream tonight? Go to %KUMIFYURL% to document your dreams!", recipient=self.request.user, app="dreams") + obj = form.save(commit=False) + obj.notification = notification + return super().form_valid(form) + + def get_success_url(self): + return reverse_lazy("dreams:notification_list") + + +class NotificationEditView(LoginRequiredMixin, UpdateView): + template_name = "dreams/notification_edit.html" + model = NotificationDailySchedule + fields = ["time"] + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["title"] = "Edit Notification" + context["subtitle"] = "Change the time of a daily notification." + context["buttons"] = [(reverse_lazy("dreams:notification_delete", args=[self.kwargs["id"]]), "Delete Notification")] + return context + + def get_success_url(self): + return reverse_lazy("dreams:notification_list") + + def get_object(self): + return get_object_or_404(NotificationDailySchedule, notification__recipient=self.request.user, id=self.kwargs["id"]) + + +class NotificationDeleteView(LoginRequiredMixin, DeleteView): + template_name = "dreams/notification_delete.html" + model = NotificationDailySchedule + + def get_object(self): + return get_object_or_404(NotificationDailySchedule, notification__recipient=self.request.user, id=self.kwargs["id"]) + + def delete(self, request, *args, **kwargs): + self.object = self.get_object() + success_url = self.get_success_url() + self.object.notification.delete() + return HttpResponseRedirect(success_url) + + def get_success_url(self): + return reverse_lazy("dreams:notification_list") \ No newline at end of file diff --git a/frontend/templates/frontend/sidebar.html b/frontend/templates/frontend/sidebar.html index fded251..8907483 100644 --- a/frontend/templates/frontend/sidebar.html +++ b/frontend/templates/frontend/sidebar.html @@ -55,6 +55,35 @@ Notifications + + + + + + + + + + + + + + + diff --git a/kumify/urls.py b/kumify/urls.py index 4888ee9..cd5cc14 100644 --- a/kumify/urls.py +++ b/kumify/urls.py @@ -25,4 +25,5 @@ urlpatterns = [ path('mood/', include("mood.urls", "mood")), path('cron/', include("cronhandler.urls", "cron")), path('webhooks/telegram/', TelegramWebhookView.as_view()), + path('dreams/', include("dreams.urls", "dreams")), ]