From 8c4d67c9a650736f957287c3abf6a39a62437319 Mon Sep 17 00:00:00 2001 From: Alex Zhidkov Date: Tue, 4 Oct 2022 14:57:28 +0300 Subject: [PATCH] solution --- .idea/dataSources.xml | 17 ++++ .idea/git_toolbox_prj.xml | 15 +++ .idea/todo-list-test-task.iml | 17 ++++ db.sqlite3 | Bin 0 -> 159744 bytes manage.py | 22 ++++ requirements.txt | 3 + static/css/styles.css | 24 +++++ templates/base.html | 41 ++++++++ templates/includes/pagination.html | 17 ++++ templates/includes/sidebar.html | 4 + templates/todo/tag_confirm_delete.html | 14 +++ templates/todo/tag_form.html | 12 +++ templates/todo/tag_list.html | 40 ++++++++ templates/todo/task_confirm_delete.html | 13 +++ templates/todo/task_form.html | 12 +++ templates/todo/task_list.html | 68 +++++++++++++ todo/__init__.py | 0 todo/admin.py | 13 +++ todo/apps.py | 6 ++ todo/forms.py | 22 ++++ todo/migrations/__init__.py | 0 todo/models.py | 25 +++++ todo/tests.py | 3 + todo/urls.py | 31 ++++++ todo/views.py | 75 ++++++++++++++ todo_list/__init__.py | 0 todo_list/asgi.py | 14 +++ todo_list/settings.py | 129 ++++++++++++++++++++++++ todo_list/urls.py | 24 +++++ todo_list/wsgi.py | 14 +++ 30 files changed, 675 insertions(+) create mode 100644 .idea/dataSources.xml create mode 100644 .idea/git_toolbox_prj.xml create mode 100644 .idea/todo-list-test-task.iml create mode 100644 db.sqlite3 create mode 100644 manage.py create mode 100644 requirements.txt create mode 100644 static/css/styles.css create mode 100644 templates/base.html create mode 100644 templates/includes/pagination.html create mode 100644 templates/includes/sidebar.html create mode 100644 templates/todo/tag_confirm_delete.html create mode 100644 templates/todo/tag_form.html create mode 100644 templates/todo/tag_list.html create mode 100644 templates/todo/task_confirm_delete.html create mode 100644 templates/todo/task_form.html create mode 100644 templates/todo/task_list.html create mode 100644 todo/__init__.py create mode 100644 todo/admin.py create mode 100644 todo/apps.py create mode 100644 todo/forms.py create mode 100644 todo/migrations/__init__.py create mode 100644 todo/models.py create mode 100644 todo/tests.py create mode 100644 todo/urls.py create mode 100644 todo/views.py create mode 100644 todo_list/__init__.py create mode 100644 todo_list/asgi.py create mode 100644 todo_list/settings.py create mode 100644 todo_list/urls.py create mode 100644 todo_list/wsgi.py diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..6d03cb8 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,17 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$PROJECT_DIR$/db.sqlite3 + $ProjectFileDir$ + + + file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.36.0.3/sqlite-jdbc-3.36.0.3.jar + + + + + \ No newline at end of file diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml new file mode 100644 index 0000000..02b915b --- /dev/null +++ b/.idea/git_toolbox_prj.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/todo-list-test-task.iml b/.idea/todo-list-test-task.iml new file mode 100644 index 0000000..740b48d --- /dev/null +++ b/.idea/todo-list-test-task.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..045536532a0a8d364a2f48123654519e6bcdb215 GIT binary patch literal 159744 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 literal 0 HcmV?d00001 diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..c471f43 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "todo_list.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..300d9ff --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +django==4.0.2 +django-debug-toolbar==3.2.4 +django-crispy-forms==1.14.0 diff --git a/static/css/styles.css b/static/css/styles.css new file mode 100644 index 0000000..74a4cb3 --- /dev/null +++ b/static/css/styles.css @@ -0,0 +1,24 @@ +body { + margin-top: 20px; +} + +.btn-blurred { + background-color: grey; + color: white; +} + +.done { + color: green; +} + +.not-done { + color: red; +} + +.text-tag { + color: grey; +} + +.text-deadline { + color: #de5416; +} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..dd0e485 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,41 @@ + + + + + {% block title %}Todo list{% endblock %} + + + + + {% load static %} + + + + + +
+
+
+ + {% block sidebar %} + {% include "includes/sidebar.html" %} + {% endblock %} + +
+
+ + {% block content %}{% endblock %} + + {% block pagination %} + {% include "includes/pagination.html" %} + {% endblock %} + +
+
+
+ + + \ No newline at end of file diff --git a/templates/includes/pagination.html b/templates/includes/pagination.html new file mode 100644 index 0000000..118c280 --- /dev/null +++ b/templates/includes/pagination.html @@ -0,0 +1,17 @@ +{% if is_paginated %} +
    + {% if page_obj.has_previous %} +
  • + prev +
  • + {% endif %} +
  • + {{ page_obj.number }} of {{ paginator.num_pages }} +
  • + {% if page_obj.has_next %} +
  • + next +
  • + {% endif %} +
+{% endif %} diff --git a/templates/includes/sidebar.html b/templates/includes/sidebar.html new file mode 100644 index 0000000..489d4f6 --- /dev/null +++ b/templates/includes/sidebar.html @@ -0,0 +1,4 @@ + diff --git a/templates/todo/tag_confirm_delete.html b/templates/todo/tag_confirm_delete.html new file mode 100644 index 0000000..73bbb46 --- /dev/null +++ b/templates/todo/tag_confirm_delete.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block content %} + +

Delete tag

+ + +

Are you sure you want to delete the tag {{ tag }}

+ +
+ {% csrf_token %} + +
+{% endblock %} diff --git a/templates/todo/tag_form.html b/templates/todo/tag_form.html new file mode 100644 index 0000000..dc2a924 --- /dev/null +++ b/templates/todo/tag_form.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% load crispy_forms_filters %} + +{% block content %} +

{{ object|yesno:"Update,Create" }} Tag

+ +
+ {% csrf_token %} + {{ form|crispy }} + +
+{% endblock %} diff --git a/templates/todo/tag_list.html b/templates/todo/tag_list.html new file mode 100644 index 0000000..11009b9 --- /dev/null +++ b/templates/todo/tag_list.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} + +{% block content %} +

Tags

+ + + {% if tag_list %} + + + + + + + {% for tag in tag_list %} + + + + + + + {% endfor %} + +
NameUpdateDelete
{{ tag.name }} + + + +
+ + {% else %} +

There are no todos

+ {% endif %} + +{% endblock %} diff --git a/templates/todo/task_confirm_delete.html b/templates/todo/task_confirm_delete.html new file mode 100644 index 0000000..009867e --- /dev/null +++ b/templates/todo/task_confirm_delete.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% block content %} + +

Delete task

+ +

Are you sure you want to delete the task {{ task }}

+ +
+ {% csrf_token %} + +
+{% endblock %} diff --git a/templates/todo/task_form.html b/templates/todo/task_form.html new file mode 100644 index 0000000..12ab127 --- /dev/null +++ b/templates/todo/task_form.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% load crispy_forms_filters %} + +{% block content %} +

{{ object|yesno:"Update,Create" }} Task

+ +
+ {% csrf_token %} + {{ form|crispy }} + +
+{% endblock %} diff --git a/templates/todo/task_list.html b/templates/todo/task_list.html new file mode 100644 index 0000000..ebedb04 --- /dev/null +++ b/templates/todo/task_list.html @@ -0,0 +1,68 @@ +{% extends "base.html" %} + +{% block content %} +

TODO list

+ +
+ + {% if task_list %} + {% for task in task_list %} +
+
+ {{ task.content }} + + + {% if task.is_done %} + Done +
+ {% csrf_token %} + + + +
+ + {% else %} + Not done + +
+ {% csrf_token %} + +
+ + {% endif %} + +
+
+ Created: {{ task.created_at }} {% if task.deadline %} + Deadline: {{ task.deadline }} + {% endif %} +
+
+ Tags: + {% for tag in task.tags.all %} + {{ tag.name }} + {% endfor %} + + + + Update + Delete +
+ {% endfor %} + + {% else %} +

There are no todos

+ {% endif %} + + + +{% endblock %} diff --git a/todo/__init__.py b/todo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/todo/admin.py b/todo/admin.py new file mode 100644 index 0000000..be0bbd4 --- /dev/null +++ b/todo/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin + +from todo.models import Tag, Task + + +@admin.register(Task) +class TaskAdmin(admin.ModelAdmin): + pass + + +@admin.register(Tag) +class AdminTag(admin.ModelAdmin): + pass diff --git a/todo/apps.py b/todo/apps.py new file mode 100644 index 0000000..c6fe8a1 --- /dev/null +++ b/todo/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TodoConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "todo" diff --git a/todo/forms.py b/todo/forms.py new file mode 100644 index 0000000..7e63ecd --- /dev/null +++ b/todo/forms.py @@ -0,0 +1,22 @@ +from django import forms +from django.core.exceptions import ValidationError + +from todo.models import Tag, Task + + +class TagForm(forms.ModelForm): + class Meta: + model = Tag + fields = "__all__" + + +class TaskForm(forms.ModelForm): + tags = forms.ModelMultipleChoiceField( + queryset=Tag.objects.all(), + widget=forms.CheckboxSelectMultiple, + required=False + ) + + class Meta: + model = Task + fields = "__all__" diff --git a/todo/migrations/__init__.py b/todo/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/todo/models.py b/todo/models.py new file mode 100644 index 0000000..4eafa24 --- /dev/null +++ b/todo/models.py @@ -0,0 +1,25 @@ +from django.db import models + + +class Tag(models.Model): + name = models.CharField(max_length=100) + + class Meta: + ordering = ["name"] + + def __str__(self): + return f"{self.name}" + + +class Task(models.Model): + content = models.CharField(blank=True, max_length=15) + created_at = models.DateTimeField(auto_now_add=True) + deadline = models.DateTimeField() + is_done = models.BooleanField(default=False) + tags = models.ManyToManyField(to=Tag, related_name="tasks") + + class Meta: + ordering = ["created_at", "is_done"] + + def __str__(self): + return f"Content: {self.content}" diff --git a/todo/tests.py b/todo/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/todo/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/todo/urls.py b/todo/urls.py new file mode 100644 index 0000000..73d72f6 --- /dev/null +++ b/todo/urls.py @@ -0,0 +1,31 @@ +from django.urls import path + +from .views import ( + TaskListView, + TaskCreateView, + TaskUpdateView, + TaskDeleteView, + TagListView, + TagCreateView, + TagUpdateView, + TagDeleteView, + TaskChangeStatusView, + +) + +urlpatterns = [ + path("", TaskListView.as_view(), name="task-list"), + path("task/create", TaskCreateView.as_view(), name="task-create"), + path("task//update", TaskUpdateView.as_view(), name="task-update"), + path("task//delete", TaskDeleteView.as_view(), name="task-delete"), + + path("tags/", TagListView.as_view(), name="tag-list"), + path("tags/create", TagCreateView.as_view(), name="tag-create"), + path("tags//update", TagUpdateView.as_view(), name="tag-update"), + path("tags//delete", TagDeleteView.as_view(), name="tag-delete"), + + path('chng_st/', TaskChangeStatusView.as_view(), name='task_change_status'), + +] + +app_name = "todo" diff --git a/todo/views.py b/todo/views.py new file mode 100644 index 0000000..1d1308c --- /dev/null +++ b/todo/views.py @@ -0,0 +1,75 @@ +from django.shortcuts import get_object_or_404, redirect +from django.urls import reverse_lazy +from django.views import generic, View + +from todo.forms import TagForm, TaskForm +from todo.models import Task, Tag + + +class TaskListView(generic.ListView): + model = Task + context_object_name = "task_list" + template_name = "todo/task_list.html" + paginate_by = 3 + queryset = Task.objects.prefetch_related("tags") + + +class TaskCreateView(generic.CreateView): + model = Task + form_class = TaskForm + template_name = "todo/task_form.html" + success_url = "http://127.0.0.1:8000/task/create" + + +class TaskUpdateView(generic.UpdateView): + model = Task + form_class = TaskForm + template_name = "todo/task_form.html" + success_url = reverse_lazy("todo:task-list") + + +class TaskDeleteView(generic.DeleteView): + model = Task + fields = "__all__" + template_name = "todo/task_confirm_delete.html" + success_url = reverse_lazy("todo:task-list") + + +class TagListView(generic.ListView): + model = Tag + context_object_name = "tag_list" + template_name = "todo/tag_list.html" + paginate_by = 3 + + +class TagCreateView(generic.CreateView): + model = Tag + form_class = TagForm + template_name = "todo/tag_form.html" + success_url = "http://127.0.0.1:8000/tags/create" + + +class TagUpdateView(generic.UpdateView): + model = Tag + form_class = TagForm + template_name = "todo/tag_form.html" + success_url = reverse_lazy("todo:tag-list") + + +class TagDeleteView(generic.DeleteView): + model = Tag + fields = "__all__" + template_name = "todo/tag_confirm_delete.html" + success_url = reverse_lazy("todo:tag-list") + + +class TaskChangeStatusView(View): + def post(self, request, pk): + task = Task.objects.get(id=pk) + task.is_done = not task.is_done + task.save() + + return redirect(reverse_lazy('todo:task-list'), permanent=True) + + def get(self, request): + return redirect(reverse_lazy('todo:task-list'), permanent=True) diff --git a/todo_list/__init__.py b/todo_list/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/todo_list/asgi.py b/todo_list/asgi.py new file mode 100644 index 0000000..d787435 --- /dev/null +++ b/todo_list/asgi.py @@ -0,0 +1,14 @@ +""" +ASGI config for todo_list project. +It exposes the ASGI callable as a module-level variable named ``application``. +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "todo_list.settings") + +application = get_asgi_application() diff --git a/todo_list/settings.py b/todo_list/settings.py new file mode 100644 index 0000000..8c8d588 --- /dev/null +++ b/todo_list/settings.py @@ -0,0 +1,129 @@ +""" +Django settings for todo_list project. +Generated by 'django-admin startproject' using Django 4.0.4. +For more information on this file, see +https://docs.djangoproject.com/en/4.0/topics/settings/ +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.0/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure-r#4o00zb3g*%diq!@(qe7n*u4(u6z!$z@3cm7ch*npwr=@qr7s" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + +INTERNAL_IPS = [ + "127.0.0.1", +] +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "debug_toolbar", + "crispy_forms", + "todo", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "debug_toolbar.middleware.DebugToolbarMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "todo_list.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [BASE_DIR / "templates"], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +CRISPY_TEMPLATE_PACK = "bootstrap4" + +WSGI_APPLICATION = "todo_list.wsgi.application" + +# Database +# https://docs.djangoproject.com/en/4.0/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + +# Password validation +# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + +# Internationalization +# https://docs.djangoproject.com/en/4.0/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "Europe/Kiev" + +USE_I18N = True + +USE_TZ = True + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.0/howto/static-files/ + +STATIC_URL = "/static/" + +STATICFILES_DIRS = [ + BASE_DIR / "static", +] + +STATIC_ROOT = BASE_DIR / "staticfiles" + +# Default primary key field type +# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/todo_list/urls.py b/todo_list/urls.py new file mode 100644 index 0000000..6403d0a --- /dev/null +++ b/todo_list/urls.py @@ -0,0 +1,24 @@ +"""todo_list URL Configuration +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path, include +from django.conf import settings +from django.conf.urls.static import static + +urlpatterns = [ + path("admin/", admin.site.urls), + path("", include("todo.urls", namespace="todo")), + path('__debug__/', include('debug_toolbar.urls')), +] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/todo_list/wsgi.py b/todo_list/wsgi.py new file mode 100644 index 0000000..1ed3200 --- /dev/null +++ b/todo_list/wsgi.py @@ -0,0 +1,14 @@ +""" +WSGI config for todo_list project. +It exposes the WSGI callable as a module-level variable named ``application``. +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "todo_list.settings") + +application = get_wsgi_application()