├── server
├── core
│ ├── __init__.py
│ ├── migrations
│ │ ├── __init__.py
│ │ ├── 0003_remove_tasklist_user.py
│ │ ├── 0005_auto_20210320_2003.py
│ │ ├── 0002_task_tasklist.py
│ │ ├── 0004_auto_20210304_1725.py
│ │ └── 0001_initial.py
│ ├── tests.py
│ ├── apps.py
│ ├── admin.py
│ ├── models.py
│ └── views.py
├── server
│ ├── __init__.py
│ ├── asgi.py
│ ├── wsgi.py
│ ├── urls.py
│ └── settings.py
├── manage.py
├── README.md
└── requirements.txt
├── client
├── README.md
├── .DS_Store
├── images
│ ├── background-image.jpg
│ └── background-bigger-screen.jpg
├── icons
│ ├── checkbox.svg
│ ├── hamburguer-menu.svg
│ ├── add.svg
│ ├── plus.svg
│ ├── calendar.svg
│ ├── star-on.svg
│ ├── checkbox-on.svg
│ ├── close-button.svg
│ ├── search-button.svg
│ ├── blue-star.svg
│ ├── star.svg
│ └── lamp-icon.svg
├── app.js
├── index.html
└── main.css
├── Procfile
├── README.md
├── manage.py
├── .gitignore
└── requirements.txt
/server/core/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/server/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # vanilla-todo
2 |
--------------------------------------------------------------------------------
/server/core/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/core/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | release: python manage.py migrate --noinput
2 | web: gunicorn server.wsgi --log-file -
--------------------------------------------------------------------------------
/client/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victormachadogp/vanilla-todo/HEAD/client/.DS_Store
--------------------------------------------------------------------------------
/server/core/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class CoreConfig(AppConfig):
5 | name = 'core'
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vanilla-todo
2 | Precisamos escrever sobre o projeto aqui... tecnologias utilizadas, como rodar o projeto e etc.
3 |
--------------------------------------------------------------------------------
/client/images/background-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victormachadogp/vanilla-todo/HEAD/client/images/background-image.jpg
--------------------------------------------------------------------------------
/client/images/background-bigger-screen.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victormachadogp/vanilla-todo/HEAD/client/images/background-bigger-screen.jpg
--------------------------------------------------------------------------------
/server/core/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import Tasklist, Task
3 |
4 | # Register your models here.
5 |
6 | admin.site.register(Tasklist)
7 | admin.site.register(Task)
8 |
--------------------------------------------------------------------------------
/client/icons/checkbox.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/client/icons/hamburguer-menu.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/server/core/migrations/0003_remove_tasklist_user.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.7 on 2021-02-28 19:50
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('core', '0002_task_tasklist'),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(
14 | model_name='tasklist',
15 | name='user',
16 | ),
17 | ]
18 |
--------------------------------------------------------------------------------
/server/server/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for server project.
3 |
4 | It exposes the ASGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.asgi import get_asgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/server/server/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for server project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/client/icons/add.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/client/icons/plus.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/server/core/migrations/0005_auto_20210320_2003.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.7 on 2021-03-20 23:03
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('core', '0004_auto_20210304_1725'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='task',
15 | name='id',
16 | field=models.AutoField(primary_key=True, serialize=False),
17 | ),
18 | migrations.AlterField(
19 | model_name='tasklist',
20 | name='id',
21 | field=models.AutoField(primary_key=True, serialize=False),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | """Run administrative tasks."""
9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')
10 | try:
11 | from django.core.management import execute_from_command_line
12 | except ImportError as exc:
13 | raise ImportError(
14 | "Couldn't import Django. Are you sure it's installed and "
15 | "available on your PYTHONPATH environment variable? Did you "
16 | "forget to activate a virtual environment?"
17 | ) from exc
18 | execute_from_command_line(sys.argv)
19 |
20 |
21 | if __name__ == '__main__':
22 | main()
23 |
--------------------------------------------------------------------------------
/server/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | """Run administrative tasks."""
9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')
10 | try:
11 | from django.core.management import execute_from_command_line
12 | except ImportError as exc:
13 | raise ImportError(
14 | "Couldn't import Django. Are you sure it's installed and "
15 | "available on your PYTHONPATH environment variable? Did you "
16 | "forget to activate a virtual environment?"
17 | ) from exc
18 | execute_from_command_line(sys.argv)
19 |
20 |
21 | if __name__ == '__main__':
22 | main()
23 |
--------------------------------------------------------------------------------
/client/icons/calendar.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/client/icons/star-on.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/client/icons/checkbox-on.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Editors
2 | .vscode/
3 | .idea/
4 |
5 | # Mac/OSX
6 | .DS_Store
7 |
8 | # Windows
9 | Thumbs.db
10 |
11 | # Source for the following rules: https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore
12 | # Byte-compiled / optimized / DLL files
13 | __pycache__/
14 | *.py[cod]
15 | *$py.class
16 |
17 | # Distribution / packaging
18 | .Python
19 | build/
20 | develop-eggs/
21 | dist/
22 | downloads/
23 | eggs/
24 | .eggs/
25 | lib/
26 | lib64/
27 | parts/
28 | sdist/
29 | var/
30 | wheels/
31 | *.egg-info/
32 | .installed.cfg
33 | *.egg
34 | MANIFEST
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Django stuff:
41 | *.log
42 | local_settings.py
43 | db.sqlite3
44 |
45 | # pyenv
46 | .python-version
47 |
48 | # Environments
49 | .env
50 | .venv
51 | env/
52 | venv/
53 | ENV/
54 | env.bak/
55 | venv.bak/
56 |
57 |
--------------------------------------------------------------------------------
/server/server/urls.py:
--------------------------------------------------------------------------------
1 | """server URL Configuration
2 | The `urlpatterns` list routes URLs to views. For more information please see:
3 | https://docs.djangoproject.com/en/3.1/topics/http/urls/
4 | Examples:
5 | Function views
6 | 1. Add an import: from my_app import views
7 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
8 | Class-based views
9 | 1. Add an import: from other_app.views import Home
10 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
11 | Including another URLconf
12 | 1. Import the include() function: from django.urls import include, path
13 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
14 | """
15 |
16 |
17 | from django.contrib import admin
18 | from django.urls import path
19 | from core import views
20 |
21 |
22 | urlpatterns = [
23 | path('admin', admin.site.urls),
24 | path('tasklists', views.tasklists),
25 | path('tasklists/', views.tasklistsid),
26 | path('tasklists//tasks', views.tasklists_id_tasks),
27 | path('task/', views.task_id)
28 | ]
29 |
--------------------------------------------------------------------------------
/client/icons/close-button.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/server/core/migrations/0002_task_tasklist.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.7 on 2021-02-28 19:44
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('core', '0001_initial'),
12 | ]
13 |
14 | operations = [
15 | migrations.CreateModel(
16 | name='Tasklist',
17 | fields=[
18 | ('id', models.IntegerField(primary_key=True, serialize=False)),
19 | ('title', models.CharField(max_length=20)),
20 | ('color', models.CharField(max_length=6)),
21 | ('order', models.IntegerField()),
22 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
23 | ],
24 | ),
25 | migrations.CreateModel(
26 | name='Task',
27 | fields=[
28 | ('id', models.IntegerField(primary_key=True, serialize=False)),
29 | ('title', models.CharField(max_length=20)),
30 | ('description', models.TextField()),
31 | ('active', models.BooleanField()),
32 | ('tasklist', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.tasklist')),
33 | ],
34 | ),
35 | ]
36 |
--------------------------------------------------------------------------------
/client/app.js:
--------------------------------------------------------------------------------
1 | // Navbar
2 | let btnNav = document.querySelector(".btn-nav");
3 | let navContainer = document.querySelector(".nav-menu");
4 | let btnClose = document.querySelector(".btn-close");
5 |
6 | btnNav.addEventListener("click", openNavMenu);
7 | btnClose.addEventListener("click", closeNavMenu);
8 |
9 | function openNavMenu() {
10 | navContainer.style.display = "block";
11 | }
12 |
13 | function closeNavMenu() {
14 | navContainer.style.display = "none";
15 | }
16 |
17 | // Fetch API
18 | const apiBaseUrl = 'https://jsonplaceholder.typicode.com/todos'
19 | let todosContainer = document.querySelector('.todos-container')
20 |
21 | getAllTodos()
22 |
23 | async function getAllTodos () {
24 | const response = await fetch(`${apiBaseUrl}`)
25 | const todos = await response.json()
26 |
27 | todos.map((todo) => {
28 | const todoContent = `
29 |
30 |
31 |

32 |

33 |
${todo.title}
34 |
35 |

36 |

37 |
38 | `
39 |
40 | todosContainer.innerHTML += todoContent
41 | })
42 | }
--------------------------------------------------------------------------------
/client/icons/search-button.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/server/core/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.contrib.auth.models import AbstractUser
3 | from datetime import date
4 |
5 | class User(AbstractUser):
6 | pass
7 |
8 | # Create your models here.
9 |
10 |
11 | class Tasklist(models.Model):
12 | # id = models.IntegerField(primary_key=True)
13 | id = models.AutoField(primary_key=True)
14 | # user = models.ForeignKey(User, on_delete=models.CASCADE)
15 | title = models.CharField(max_length=20)
16 | color = models.CharField(max_length=6)
17 | order = models.IntegerField()
18 |
19 | def __str__(self):
20 | return self.title
21 |
22 | class Task(models.Model):
23 | # id = models.IntegerField(primary_key=True)
24 | id = models.AutoField(primary_key=True)
25 | tasklist = models.ForeignKey(Tasklist, on_delete=models.CASCADE)
26 | title = models.CharField(max_length=20)
27 | description = models.TextField(max_length=255)
28 | completed = models.BooleanField(null=True, blank=True)
29 | watch = models.BooleanField(null=True, blank=True)
30 | due_date = models.DateField(null=True, blank=True)
31 | due_time = models.TimeField(null=True, blank=True)
32 | order = models.IntegerField(null=True)
33 | creation_at = models.DateField(auto_now_add=True)
34 | update_at = models.DateField(auto_now=True)
35 |
36 | def __str__(self):
37 | return self.title
38 |
39 | class Meta:
40 | verbose_name = "Nota"
41 | verbose_name_plural = "Notas"
--------------------------------------------------------------------------------
/client/icons/blue-star.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/client/icons/star.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | # Instalação
2 |
3 | Versão do Python 3.9.2
4 |
5 | Para criar o ambiente virtual Python, entre na pasta `server` e digite:
6 |
7 | > python -m venv venv
8 |
9 | Para **ativar** o ambiente virtual, digite:
10 |
11 | Observação: O ponto '.' no inicio do comando so deve ser usado em terminais 'bash'.
12 |
13 | #### No Windows:
14 | >. venv/Scripts/activate
15 |
16 | #### No Linux ou MacOS:
17 | >source venv/bin/activate
18 |
19 | Você vai observar que o ambientenvirtual está ativo pq vai aparecer o nome da maquina virtual assim: (venv)
20 |
21 | Em seguida, instale os requirements:
22 | > pip install --require-hashes -r requirements.txt
23 |
24 | Rode as migrations:
25 | > python manage.py migrate
26 |
27 | .
28 |
29 | # Ambiente de desenvolvimento
30 |
31 | Para rodar o projeto:
32 | > python manage.py runserver
33 |
34 | Agora vc pode acessar as rotas localmente
35 |
36 | E para acessar o admin, não pare o servidor, em outro terminal, ative a maquina virtual e rode o comando:
37 | >
38 | > (venv) $ python manage.py createsuperuser
39 |
40 | ```
41 | Username:
42 | Email address: seuemail@email.com
43 | Password:
44 | Password (again):
45 | ```
46 | Se tudo estiver okay, vai aparecer
47 | ```
48 | Superuser created successfully.
49 | ```
50 | # Importante!
51 | Caso instale um novo pacote e necessite criar um novo requirements.txt siga os passos abaixo:
52 |
53 |
54 | Primeiro crie o arquivo requirements.txt
55 | > pip freeze > requirements.txt
56 |
57 |
58 | Em seguida o comando abaixo irá gerar os hashes.
59 | > pip-compile requirements.txt --generate-hashes --output-file requirements.txt
60 |
61 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | #
2 | # This file is autogenerated by pip-compile
3 | # To update, run:
4 | #
5 | # pip-compile --generate-hashes --output-file=requirements.txt requirements.txt
6 | #
7 | asgiref==3.3.1 \
8 | --hash=sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17 \
9 | --hash=sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0
10 | # via
11 | # -r requirements.txt
12 | # django
13 | click==7.1.2 \
14 | --hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a \
15 | --hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc
16 | # via
17 | # -r requirements.txt
18 | # pip-tools
19 | django==3.1.7 \
20 | --hash=sha256:32ce792ee9b6a0cbbec340123e229ac9f765dff8c2a4ae9247a14b2ba3a365a7 \
21 | --hash=sha256:baf099db36ad31f970775d0be5587cc58a6256a6771a44eb795b554d45f211b8
22 | # via -r requirements.txt
23 | gunicorn==20.1.0 \
24 | --hash=sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8
25 | # via -r requirements.txt
26 | pip-tools==5.5.0 \
27 | --hash=sha256:10841c1e56c234d610d0466447685b9ea4ee4a2c274f858c0ef3c33d9bd0d985 \
28 | --hash=sha256:cb0108391366b3ef336185097b3c2c0f3fa115b15098dafbda5e78aef70ea114
29 | # via -r requirements.txt
30 | pytz==2021.1 \
31 | --hash=sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da \
32 | --hash=sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798
33 | # via
34 | # -r requirements.txt
35 | # django
36 | sqlparse==0.4.1 \
37 | --hash=sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0 \
38 | --hash=sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8
39 | # via
40 | # -r requirements.txt
41 | # django
42 |
43 | # WARNING: The following packages were not pinned, but pip requires them to be
44 | # pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag.
45 | # pip
46 | # setuptools
47 |
--------------------------------------------------------------------------------
/server/requirements.txt:
--------------------------------------------------------------------------------
1 | #
2 | # This file is autogenerated by pip-compile
3 | # To update, run:
4 | #
5 | # pip-compile --generate-hashes --output-file=requirements.txt requirements.txt
6 | #
7 | asgiref==3.3.1 \
8 | --hash=sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17 \
9 | --hash=sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0
10 | # via
11 | # -r requirements.txt
12 | # django
13 | click==7.1.2 \
14 | --hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a \
15 | --hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc
16 | # via
17 | # -r requirements.txt
18 | # pip-tools
19 | django==3.1.7 \
20 | --hash=sha256:32ce792ee9b6a0cbbec340123e229ac9f765dff8c2a4ae9247a14b2ba3a365a7 \
21 | --hash=sha256:baf099db36ad31f970775d0be5587cc58a6256a6771a44eb795b554d45f211b8
22 | # via -r requirements.txt
23 | gunicorn==20.1.0 \
24 | --hash=sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8
25 | # via -r requirements.txt
26 | pip-tools==5.5.0 \
27 | --hash=sha256:10841c1e56c234d610d0466447685b9ea4ee4a2c274f858c0ef3c33d9bd0d985 \
28 | --hash=sha256:cb0108391366b3ef336185097b3c2c0f3fa115b15098dafbda5e78aef70ea114
29 | # via -r requirements.txt
30 | pytz==2021.1 \
31 | --hash=sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da \
32 | --hash=sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798
33 | # via
34 | # -r requirements.txt
35 | # django
36 | sqlparse==0.4.1 \
37 | --hash=sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0 \
38 | --hash=sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8
39 | # via
40 | # -r requirements.txt
41 | # django
42 |
43 | # WARNING: The following packages were not pinned, but pip requires them to be
44 | # pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag.
45 | # pip
46 | # setuptools
47 |
--------------------------------------------------------------------------------
/client/icons/lamp-icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/server/core/migrations/0004_auto_20210304_1725.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.7 on 2021-03-04 20:25
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('core', '0003_remove_tasklist_user'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterModelOptions(
15 | name='task',
16 | options={'verbose_name': 'Nota', 'verbose_name_plural': 'Notas'},
17 | ),
18 | migrations.RemoveField(
19 | model_name='task',
20 | name='active',
21 | ),
22 | migrations.AddField(
23 | model_name='task',
24 | name='completed',
25 | field=models.BooleanField(blank=True, null=True),
26 | ),
27 | migrations.AddField(
28 | model_name='task',
29 | name='creation_at',
30 | field=models.DateField(auto_now_add=True, default=django.utils.timezone.now),
31 | preserve_default=False,
32 | ),
33 | migrations.AddField(
34 | model_name='task',
35 | name='due_date',
36 | field=models.DateField(blank=True, null=True),
37 | ),
38 | migrations.AddField(
39 | model_name='task',
40 | name='due_time',
41 | field=models.TimeField(blank=True, null=True),
42 | ),
43 | migrations.AddField(
44 | model_name='task',
45 | name='order',
46 | field=models.IntegerField(null=True),
47 | ),
48 | migrations.AddField(
49 | model_name='task',
50 | name='update_at',
51 | field=models.DateField(auto_now=True),
52 | ),
53 | migrations.AddField(
54 | model_name='task',
55 | name='watch',
56 | field=models.BooleanField(blank=True, null=True),
57 | ),
58 | migrations.AlterField(
59 | model_name='task',
60 | name='description',
61 | field=models.TextField(max_length=255),
62 | ),
63 | ]
64 |
--------------------------------------------------------------------------------
/server/core/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.7 on 2021-02-28 19:31
2 |
3 | import django.contrib.auth.models
4 | import django.contrib.auth.validators
5 | from django.db import migrations, models
6 | import django.utils.timezone
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | initial = True
12 |
13 | dependencies = [
14 | ('auth', '0012_alter_user_first_name_max_length'),
15 | ]
16 |
17 | operations = [
18 | migrations.CreateModel(
19 | name='User',
20 | fields=[
21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22 | ('password', models.CharField(max_length=128, verbose_name='password')),
23 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
24 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
25 | ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
26 | ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
27 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
28 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
29 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
30 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
31 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
32 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
33 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
34 | ],
35 | options={
36 | 'verbose_name': 'user',
37 | 'verbose_name_plural': 'users',
38 | 'abstract': False,
39 | },
40 | managers=[
41 | ('objects', django.contrib.auth.models.UserManager()),
42 | ],
43 | ),
44 | ]
45 |
--------------------------------------------------------------------------------
/server/server/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for server project.
3 |
4 | Generated by 'django-admin startproject' using Django 3.1.7.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.1/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/3.1/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 |
15 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
16 | BASE_DIR = Path(__file__).resolve().parent.parent
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = 'jp(^lerd$*zymc_y^x=w)u=srhpwp8pfasml3k+p5m9kkna@y)'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | 'django.contrib.admin',
35 | 'django.contrib.auth',
36 | 'django.contrib.contenttypes',
37 | 'django.contrib.sessions',
38 | 'django.contrib.messages',
39 | 'django.contrib.staticfiles',
40 | 'core',
41 | ]
42 |
43 | MIDDLEWARE = [
44 | 'django.middleware.security.SecurityMiddleware',
45 | 'django.contrib.sessions.middleware.SessionMiddleware',
46 | 'django.middleware.common.CommonMiddleware',
47 | 'django.middleware.csrf.CsrfViewMiddleware',
48 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
49 | 'django.contrib.messages.middleware.MessageMiddleware',
50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
51 | ]
52 |
53 | ROOT_URLCONF = 'server.urls'
54 |
55 | TEMPLATES = [
56 | {
57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
58 | 'DIRS': [],
59 | 'APP_DIRS': True,
60 | 'OPTIONS': {
61 | 'context_processors': [
62 | 'django.template.context_processors.debug',
63 | 'django.template.context_processors.request',
64 | 'django.contrib.auth.context_processors.auth',
65 | 'django.contrib.messages.context_processors.messages',
66 | ],
67 | },
68 | },
69 | ]
70 |
71 | WSGI_APPLICATION = 'server.wsgi.application'
72 |
73 |
74 | # Database
75 | # https://docs.djangoproject.com/en/3.1/ref/settings/#databases
76 |
77 | DATABASES = {
78 | 'default': {
79 | 'ENGINE': 'django.db.backends.sqlite3',
80 | 'NAME': BASE_DIR / 'db.sqlite3',
81 | }
82 | }
83 |
84 |
85 | # Password validation
86 | # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
87 |
88 | AUTH_PASSWORD_VALIDATORS = [
89 | {
90 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
91 | },
92 | {
93 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
94 | },
95 | {
96 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
97 | },
98 | {
99 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
100 | },
101 | ]
102 |
103 |
104 | # Internationalization
105 | # https://docs.djangoproject.com/en/3.1/topics/i18n/
106 |
107 | LANGUAGE_CODE = 'en-us'
108 |
109 | TIME_ZONE = 'America/Sao_Paulo'
110 |
111 | USE_I18N = True
112 |
113 | USE_L10N = True
114 |
115 | USE_TZ = True
116 |
117 |
118 | # Static files (CSS, JavaScript, Images)
119 | # https://docs.djangoproject.com/en/3.1/howto/static-files/
120 |
121 | STATIC_URL = '/static/'
122 |
123 | AUTH_USER_MODEL = 'core.User'
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 | Vanilla ToDo
15 |
16 |
17 |
18 |
19 |
62 |
63 |
64 |
65 |
66 |
Vanilla Todo
67 |
Quinta-Feira, 26 Mar
68 |
69 |
70 |
71 |
72 |
75 |

76 |
77 |
78 |
79 |
80 |

81 |

82 |
Build a todo list design
83 |
84 |

85 |

86 |
87 |
88 |
89 |

90 |

91 |
Buy travel guide
92 |
93 |

94 |

95 |
96 |
97 |

98 |
Add new to-do
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/client/main.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0px;
3 | padding: 0px;
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | font-family: "Roboto", sans-serif;
9 | }
10 |
11 | .container {
12 | display: grid;
13 | grid-template-columns: 1fr 4fr;
14 | grid-template-rows: 100vh;
15 | max-width: 2000px;
16 | margin: 0 auto;
17 | background-color: white;
18 | }
19 |
20 | .nav-menu {
21 | background-color: white;
22 | box-shadow: 1px 4px 6px 1px #8f8f8f26;
23 | z-index: 9999;
24 | color: #505050;
25 | }
26 |
27 | .main-content {
28 | display: grid;
29 | grid-template-rows: 25%;
30 | }
31 |
32 | .image-background {
33 | position: relative;
34 | }
35 |
36 | .title-box {
37 | position: absolute;
38 | bottom: 0px;
39 | margin: 25px;
40 | color: white;
41 | }
42 |
43 | .list-title {
44 | font-size: 1.8rem;
45 | font-weight: 600;
46 |
47 | }
48 |
49 | .date {
50 | margin-top: 0.3rem;
51 | font-weight: 100;
52 |
53 | }
54 |
55 | .top-image {
56 | width: 100%;
57 | height: 100%;
58 | object-fit: cover;
59 | }
60 |
61 | /* NAV LEFT MENU */
62 |
63 | .nav-list-item {
64 | display: flex;
65 | align-items: center;
66 | justify-content: space-between;
67 | padding: 14px 19px;
68 | cursor: pointer;
69 | }
70 |
71 | .list-text-color {
72 | color: #4b92c5;
73 | }
74 |
75 | .nav-list-item:hover {
76 | background-color: #f3f9fd;
77 | }
78 |
79 | .nav-item-info {
80 | display: flex;
81 | align-items: center;
82 | }
83 |
84 | .list-items-remaining {
85 | margin-right: 8px;
86 | }
87 |
88 | .nav-highlighted {
89 | background-color: #ecf5fa;
90 | }
91 |
92 | .nav-list-icon {
93 | display: flex;
94 | align-items: center;
95 | }
96 |
97 | .user-info {
98 | display: flex;
99 | align-items: center;
100 | }
101 |
102 | .user-photo {
103 | width: 26px;
104 | height: 26px;
105 | background-color: grey;
106 | border-radius: 50px;
107 | }
108 |
109 | .nav-item-text {
110 | padding: 0px 10px;
111 | font-size: 0.95rem;
112 | }
113 |
114 | .todos-container {
115 | width: 90%;
116 | max-width: 1430px;
117 | margin: auto;
118 | height: 100%;
119 | overflow-y: auto;
120 | padding-bottom: 85px;
121 | }
122 | .todos-container::-webkit-scrollbar {
123 | display: none;
124 | }
125 | .todo-card {
126 | display: flex;
127 | justify-content: space-between;
128 | padding: 20px 0;
129 | border-bottom: 1px solid #efefef;
130 | }
131 | .todo-title-status-container {
132 | display: flex;
133 | align-items: center;
134 | }
135 | .todo-title-status-container img {
136 | margin-right: 14px;
137 | }
138 | .todo-card img {
139 | width: 23px;
140 | height: 23px;
141 | cursor: pointer;
142 | }
143 | .check-on,
144 | .highlight-on {
145 | display: none;
146 | }
147 | .todo-card.completed .check-off,
148 | .todo-card.highlighted .highlight-off {
149 | display: none;
150 | }
151 | .todo-card.completed .check-on,
152 | .todo-card.highlighted .highlight-on {
153 | display: block;
154 | }
155 | .todo-card.completed {
156 | text-decoration: line-through;
157 | color: #828282;
158 | }
159 | .add-todo {
160 | position: fixed;
161 | bottom: 0;
162 | width: 72%;
163 | max-width: 1430px;
164 | background-color: #d7e1e8;
165 | padding: 15px 17px;
166 | margin-bottom: 20px;
167 | display: flex;
168 | align-items: center;
169 | color: #515cb5;
170 | cursor: pointer;
171 | }
172 | .add-todo img {
173 | width: 28px;
174 | height: 28px;
175 | margin-right: 14px;
176 | }
177 |
178 | .btn-close {
179 | display: none;
180 | }
181 |
182 | .menu-icon {
183 | display: none;
184 | }
185 |
186 | .switch-button {
187 | position: absolute;
188 | top: 0;
189 | right: 0;
190 | cursor: pointer;
191 | margin: 25px 35px;
192 |
193 | }
194 |
195 | .lamp-icon {
196 | width: 30px;
197 | }
198 |
199 | @media (max-width: 1024px) {
200 | .btn-nav {
201 | position: absolute;
202 | margin: 35px 25px;
203 | cursor: pointer;
204 |
205 | }
206 |
207 | .switch-button {
208 | margin: 25px 25px;
209 | }
210 |
211 | .menu-icon {
212 | display: block;
213 | }
214 |
215 | .container {
216 | grid-template-columns: 1fr;
217 | }
218 |
219 | .nav-menu {
220 | display: none;
221 | position: absolute;
222 | top: 0px;
223 | height: 100vh;
224 | min-width: 250px;
225 | }
226 |
227 | .btn-close {
228 | margin: 10px;
229 | display: block;
230 | }
231 | .add-todo {
232 | width: 90%;
233 | }
234 | }
235 |
236 | @media (min-width: 1025px) {
237 | .nav-menu {
238 | display: block !important;
239 | padding-top: 35px;
240 | }
241 | }
242 |
243 | @media (min-width: 2000px){
244 | body {
245 | background-image: url("/client/images/background-bigger-screen.jpg");
246 | background-repeat: no-repeat;
247 | background-size: cover;
248 |
249 | }
250 | }
--------------------------------------------------------------------------------
/server/core/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 | from django.http import HttpResponse, JsonResponse
3 | from django.views.decorators.http import require_http_methods
4 | from django.views.decorators.csrf import csrf_exempt
5 | from django.forms.models import model_to_dict
6 | from django.core import serializers
7 | from .models import Task, Tasklist
8 | from django.db.utils import IntegrityError
9 | import sys, json, logging
10 |
11 |
12 | @csrf_exempt
13 | @require_http_methods(["GET","POST"])
14 | def tasklists(request):
15 | if request.method == "GET":
16 | try:
17 | tasks = Tasklist.objects.all().values()
18 | except Tasklist.DoesNotExist:
19 | return JsonResponse(
20 | {'message': 'Tasklists do not exist'},
21 | status = 404
22 | )
23 |
24 | task_list = list(tasks)
25 | return JsonResponse(task_list, safe=False)
26 |
27 | else:
28 |
29 | data = json.loads(request.body)
30 |
31 | tsk = Tasklist()
32 | if "title" in data:
33 | tsk.title = data['title']
34 | if "color" in data:
35 | tsk.color = data['color']
36 | if "order" in data:
37 | tsk.order = data['order']
38 |
39 | if not tsk.title:
40 | return JsonResponse(
41 | {'message': 'Required Title field'},
42 | status=422
43 | )
44 |
45 | try:
46 | tsk.save()
47 | except:
48 | return JsonResponse(
49 | {'message': sys.exc_info()[0]},
50 | status=500)
51 |
52 | return JsonResponse(
53 | {'message': 'Created'},
54 | status=201
55 | )
56 |
57 |
58 | @csrf_exempt
59 | @require_http_methods(["GET", "PUT", "DELETE"])
60 | def tasklistsid(request, tasklist_id):
61 | try:
62 | tasklist = Tasklist.objects.get(id=tasklist_id)
63 | except Tasklist.DoesNotExist:
64 | return JsonResponse({'message': 'Tasklist_id does not exist'}, status=status.HTTP_404_NOT_FOUND)
65 |
66 | if request.method == "GET":
67 | return JsonResponse(model_to_dict(tasklist), safe=False)
68 | elif request.method in ("PUT", "PATCH"):
69 |
70 | data = json.loads(request.body)
71 |
72 | try:
73 | Tasklist.objects.filter(id=tasklist_id).update(**data)
74 | except IntegrityError:
75 | return HttpResponse('ERRO - Id é invalido', status=404)
76 |
77 | return HttpResponse('OK', status=200)
78 |
79 | elif request.method == "DELETE":
80 | tasklist.delete()
81 | return JsonResponse({'message': 'Tasklist deleted with success!'}, status=204)
82 |
83 | #TASKS
84 | @csrf_exempt
85 | @require_http_methods(["GET","POST"])
86 | def tasklists_id_tasks(request, tasklist_id):
87 | if request.method == "GET":
88 | tasks = Task.objects.filter(tasklist=tasklist_id).values()
89 | return JsonResponse(list(tasks), safe=False)
90 | else:
91 | data = json.loads(request.body)
92 |
93 | try:
94 | tasklist = Tasklist.objects.get(id=tasklist_id)
95 | except Tasklist.DoesNotExist:
96 | return JsonResponse({'message': ' Tasklist not found.'}, status=404)
97 |
98 | task = Task(tasklist=tasklist, title=data['title'] , description=data['description'], completed=data['completed'], watch=data['watch'], due_date=data['due_date'], due_time=data['due_time'], order=data['order'])
99 |
100 | try:
101 | task.save()
102 | except Exception as error:
103 | logging.error(error)
104 | return JsonResponse({'message': 'Error when saving'}, status=500)
105 |
106 | return JsonResponse({"message": 'Task created'}, status=201)
107 |
108 |
109 | @csrf_exempt
110 | @require_http_methods(["GET","PUT", "PATCH" ,"DELETE"])
111 | def task_id(request, task_id):
112 | if request.method == "GET":
113 | try:
114 | task = Task.objects.get(id=task_id)
115 | single_task = model_to_dict(task)
116 | except Task.DoesNotExist:
117 | return JsonResponse({'message': ' Task not found.'}, status=404)
118 |
119 | return JsonResponse(single_task, safe=False)
120 | elif request.method in ("PUT", "PATCH"):
121 |
122 | data = json.loads(request.body)
123 |
124 | try:
125 | Task.objects.filter(id=task_id).update(**data)
126 | except IntegrityError:
127 | return JsonResponse({'message': ' Task not found.'}, status=404)
128 |
129 | return JsonResponse({"message": 'Task updated'}, status=200)
130 |
131 | try:
132 | task = Task.objects.get(id=task_id)
133 | task.delete()
134 | except Task.DoesNotExist:
135 | return JsonResponse({'message': ' Task not found.'}, status=404)
136 |
137 | return JsonResponse({"message": 'Task deleted'}, status=200)
138 |
139 |
140 |
141 | # Tasklists
142 | # GET /tasklists/ - retorna todas as tasklists - ✅
143 | # GET /tasklists/:id/ - retorna apenas uma tasklist ✅
144 | # POST /tasklists/ - cria uma nova tasklist ✅
145 | # PUT/PATCH /tasklists/:id/ - atualiza apenas uma tasklist ✅
146 | # DELETE /tasklists/:id/ - deleta apenas uma tasklist ✅
147 |
148 | # Tasks
149 | # GET /tasklists/:id/tasks/ - retorna todas as tasks de uma determinada tasklist ✅
150 | # GET /tasklists/:id/tasks/:id - retorna apenas uma task dentre as tasks de uma determinada tasklist
151 | # POST /tasklists/:id/tasks/ - cria uma nova task✅
152 | # PUT/PATCH? /tasks/:id/ - atualiza apenas uma task de uma determinada tasklist
153 | # DELETE /tasks/:id/ - deleta apenas uma task✅
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------