kumify/mood/views.py
Kumi ae97cfa30e
refactor: clean up unused imports and improve code quality
Removed unnecessary imports across various modules to streamline the application's dependencies and improve loading times. Specific changes include the removal of unused Django model and admin imports in several apps, simplifying view imports by eliminating unutilized components, and cleaning up static CSS for better maintainability. Corrections were made to conditional expressions for clearer logic. The removal of the django.test.TestCase import in test files reflects a shift towards a different testing strategy or the current lack of tests. Exception handling has been made more explicit to avoid catching unintended exceptions, paving the way for more robust error handling and logging in the future. Additionally, a new CSS file was added for frontend enhancements, indicating ongoing UI/UX improvements.

These changes collectively aim to make the codebase more maintainable and efficient, reducing clutter and focusing on used functionalities. It's a step towards optimizing the application's performance and ensuring a clean, manageable codebase for future development.
2024-06-02 20:27:02 +02:00

577 lines
18 KiB
Python

from django.views.generic import (
TemplateView,
ListView,
UpdateView,
DetailView,
CreateView,
DeleteView,
View,
)
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, HttpResponse
from django.utils import timezone
from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.utils.decorators import method_decorator
from django.db.models import Count
from .models import Status, Activity, Mood, StatusMedia, StatusActivity
from .forms import StatusForm
from .statistics import moodstats, activitystats, moodpies, activitymood, activitypies
from common.helpers import get_upload_path
from common.templatetags.images import hvhtml, bkhtml
from msgio.models import NotificationDailySchedule, Notification
from dateutil import relativedelta
from datetime import datetime
import json
class StatusListView(LoginRequiredMixin, ListView):
template_name = "mood/status_list.html"
model = Status
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Status List"
context["subtitle"] = "Just a list of your mood entries."
context["buttons"] = [
(reverse_lazy("mood:status_create"), "New Status", "plus")
]
return context
def get_queryset(self):
return Status.objects.filter(user=self.request.user).order_by("timestamp")
class StatusViewView(LoginRequiredMixin, DetailView):
template_name = "mood/status_view.html"
model = Status
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "View Status"
context["subtitle"] = "View the details of your mood entry."
context["buttons"] = [
(
reverse_lazy("mood:status_edit", kwargs={"id": self.kwargs["id"]}),
"Edit Status",
"pen",
)
]
return context
def get_object(self):
return get_object_or_404(Status, user=self.request.user, id=self.kwargs["id"])
class StatusCreateView(LoginRequiredMixin, CreateView):
template_name = "mood/status_edit.html"
form_class = StatusForm
model = Status
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Create Status"
context["subtitle"] = "How are you feeling today?"
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 activity in form.cleaned_data["activities"]:
if activity.user == self.request.user:
StatusActivity.objects.create(activity=activity, status=form.instance)
for attachment in form.cleaned_data["uploads"]:
dba = StatusMedia(status=form.instance)
dba.file.save(get_upload_path(form.instance, attachment.name), attachment)
dba.save()
return ret
def get_success_url(self):
return reverse_lazy("mood:status_view", kwargs={"id": self.object.id})
class StatusEditView(LoginRequiredMixin, UpdateView):
template_name = "mood/status_edit.html"
form_class = StatusForm
model = Status
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Update Status"
context["subtitle"] = "Change a status you created before."
context["scripts"] = ["frontend/js/dropdown-to-buttons.js"]
context["buttons"] = [
(
reverse_lazy("mood:status_delete", kwargs={"id": self.kwargs["id"]}),
"Delete Status",
"trash-alt",
)
]
return context
def get_object(self):
return get_object_or_404(Status, user=self.request.user, id=self.kwargs["id"])
def form_valid(self, form):
for attachment in form.cleaned_data["uploads"]:
dba = StatusMedia(status=form.instance)
dba.file.save(get_upload_path(form.instance, attachment.name), attachment)
dba.save()
for activity in form.cleaned_data["activities"]:
if activity.user == self.request.user:
if activity not in form.instance.activity_set:
StatusActivity.objects.create(
activity=activity, status=form.instance
)
for statusactivity in form.instance.statusactivity_set.all():
if statusactivity.activity not in form.cleaned_data["activities"]:
statusactivity.delete()
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("mood:status_view", kwargs={"id": self.object.id})
class StatusDeleteView(LoginRequiredMixin, DeleteView):
template_name = "mood/status_delete.html"
model = Status
def get_object(self):
return get_object_or_404(Status, user=self.request.user, id=self.kwargs["id"])
def get_success_url(self):
return reverse_lazy("mood:status_list")
class ActivityListView(LoginRequiredMixin, ListView):
template_name = "mood/activity_list.html"
model = Activity
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Activities"
context["subtitle"] = "The activities you have defined."
context["buttons"] = [
(reverse_lazy("mood:activity_create"), "Create Activity", "pen")
]
return context
def get_queryset(self):
return Activity.objects.filter(user=self.request.user)
class ActivityEditView(LoginRequiredMixin, UpdateView):
template_name = "mood/activity_edit.html"
model = Activity
fields = ["name", "icon", "color"]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Edit Activity"
context["subtitle"] = "Make changes to the activity."
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("mood:activity_delete", kwargs={"id": self.kwargs["id"]}),
"Delete Activity",
"trash-alt",
)
]
return context
def get_object(self):
return get_object_or_404(Activity, user=self.request.user, id=self.kwargs["id"])
def get_success_url(self):
return reverse_lazy("mood:activity_list")
class ActivityCreateView(LoginRequiredMixin, CreateView):
template_name = "mood/activity_edit.html"
model = Activity
fields = ["name", "icon", "color"]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Create Activity"
context["subtitle"] = "Add a new activity."
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("mood:activity_list")
class ActivityDeleteView(LoginRequiredMixin, DeleteView):
template_name = "mood/activity_delete.html"
model = Activity
def get_object(self):
return get_object_or_404(Activity, user=self.request.user, id=self.kwargs["id"])
def get_success_url(self):
return reverse_lazy("mood:activity_list")
class MoodListView(LoginRequiredMixin, ListView):
template_name = "mood/mood_list.html"
model = Mood
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Moods"
context["subtitle"] = "The different moods you have defined."
return context
def get_queryset(self):
return Mood.objects.filter(user=self.request.user)
class MoodEditView(LoginRequiredMixin, UpdateView):
template_name = "mood/mood_edit.html"
model = Mood
fields = ["name", "icon", "color", "value"]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Edit Mood"
context["subtitle"] = "Make changes to the mood."
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 get_object(self):
return get_object_or_404(Mood, user=self.request.user, id=self.kwargs["id"])
def get_success_url(self):
return reverse_lazy("mood:mood_list")
class MoodCreateView(LoginRequiredMixin, CreateView):
template_name = "mood/mood_edit.html"
model = Mood
fields = ["name", "icon", "color", "value"]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Create Activity"
context["subtitle"] = "Add a new activity."
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("mood:activity_list")
class NotificationListView(LoginRequiredMixin, ListView):
template_name = "mood/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("mood:notification_create"), "New Notification", "plus")
]
return context
def get_queryset(self):
return NotificationDailySchedule.objects.filter(
notification__recipient=self.request.user, notification__app="mood"
)
class NotificationCreateView(LoginRequiredMixin, CreateView):
template_name = "mood/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="Hi, it's time for a new Kumify entry! Go to %KUMIFYURL% to document your mood!",
recipient=self.request.user,
app="mood",
)
obj = form.save(commit=False)
obj.notification = notification
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("mood:notification_list")
class NotificationEditView(LoginRequiredMixin, UpdateView):
template_name = "mood/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("mood:notification_delete", args=[self.kwargs["id"]]),
"Delete Notification",
)
]
return context
def get_success_url(self):
return reverse_lazy("mood: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 = "mood/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("mood:notification_list")
class MoodStatisticsView(LoginRequiredMixin, TemplateView):
template_name = "mood/statistics.html"
def get_context_data(self, **kwargs):
startdate = self.request.GET.get("start")
enddate = self.request.GET.get("end")
if enddate:
maxdate = datetime.strptime(enddate, "%Y-%m-%d")
else:
maxdate = timezone.now()
if startdate:
mindate = datetime.strptime(startdate, "%Y-%m-%d")
else:
mindate = maxdate - relativedelta.relativedelta(weeks=1) # noqa: F841
# TODO: Do something with this...?
context = super().get_context_data(**kwargs)
context["title"] = "Statistics"
context["activities"] = activitystats(self.request.user)
return context
class MoodCSVView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
res = HttpResponse(content_type="text/csv")
res["content-disposition"] = 'filename="mood.csv"'
startdate = request.GET.get("start")
enddate = request.GET.get("end")
maxdate = None
mindate = None
if enddate:
maxdate = datetime.strptime(enddate, "%Y-%m-%d")
if not startdate:
mindate = maxdate - relativedelta.relativedelta(weeks=1)
if startdate:
mindate = datetime.strptime(startdate, "%Y-%m-%d")
if not enddate:
maxdate = mindate + relativedelta.relativedelta(weeks=1)
if not maxdate:
maxdate = timezone.now()
mindate = maxdate - relativedelta.relativedelta(weeks=1)
output = "date,value"
for status in Status.objects.filter(
user=request.user, timestamp__gte=mindate, timestamp__lte=maxdate
):
if status.mood:
date = status.timestamp.strftime("%Y-%m-%d %H:%M")
output += f"\n{date},{status.mood.value}"
res.write(output)
return res
class MoodPlotView(LoginRequiredMixin, View):
@method_decorator(xframe_options_sameorigin)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
res = HttpResponse(content_type="text/html")
res.write(hvhtml(moodstats(request.user)))
return res
class MoodPiesView(LoginRequiredMixin, View):
@method_decorator(xframe_options_sameorigin)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
res = HttpResponse(content_type="text/html")
res.write(bkhtml(moodpies(request.user)))
return res
class ActivityStatisticsView(LoginRequiredMixin, TemplateView):
template_name = "mood/statistics_activity.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
activity = get_object_or_404(Activity, user=self.request.user, id=kwargs["id"])
context["title"] = "Activity Statistics for %s" % activity.name
return context
class ActivityPlotView(LoginRequiredMixin, View):
@method_decorator(xframe_options_sameorigin)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
res = HttpResponse(content_type="text/html")
res.write(
hvhtml(
activitymood(
get_object_or_404(Activity, user=request.user, id=kwargs["id"])
)
)
)
return res
class ActivityPiesView(LoginRequiredMixin, View):
@method_decorator(xframe_options_sameorigin)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
res = HttpResponse(content_type="text/html")
res.write(
bkhtml(
activitypies(
get_object_or_404(Activity, user=request.user, id=kwargs["id"])
)
)
)
return res
class MoodCountHeatmapJSONView(LoginRequiredMixin, View):
"""Returns a JSON object with the mood entries for a given time period.
This is used in conjunction with the Cal-Heatmap library to display a
heatmap of mood entries.
"""
def get(self, request, *args, **kwargs):
res = HttpResponse(content_type="application/json")
start = request.GET.get("start")
end = request.GET.get("end")
if end:
maxdate = datetime.strptime(end, "%Y-%m-%d")
else:
maxdate = timezone.now()
if start:
mindate = datetime.strptime(start, "%Y-%m-%d")
else:
mindate = maxdate - relativedelta.relativedelta(years=1)
data = (
Status.objects.filter(
user=request.user, timestamp__gte=mindate, timestamp__lte=maxdate
)
.values("timestamp__date")
.annotate(value=Count("id"))
)
data = [{"date": d["timestamp__date"].strftime("%Y-%m-%d"), "value": d["value"]} for d in data]
res.write(json.dumps(data))
return res