├── core ├── __init__.py ├── migrations │ ├── __init__.py │ └── 0001_initial.py ├── templatetags │ ├── __init__.py │ └── format_duration.py ├── templates │ ├── index.html │ ├── base_login.html │ ├── includes │ │ └── messages.html │ ├── tarefa.html │ ├── projetos.html │ ├── funcionarios.html │ ├── base.html │ └── tarefas.html ├── apps.py ├── forms.py ├── admin.py ├── static │ └── css │ │ └── estilo.css ├── urls.py ├── views.py ├── models.py └── tests │ └── test_insercoes.py ├── accounts ├── __init__.py ├── migrations │ └── __init__.py ├── tests │ ├── __init__.py │ └── test_login.py ├── models.py ├── admin.py ├── apps.py ├── urls.py ├── forms.py ├── templates │ └── accounts │ │ ├── register.html │ │ └── login.html └── views.py ├── projectmanager ├── __init__.py ├── wsgi.py ├── urls.py └── settings.py ├── requirements.txt ├── Documentação ├── Diagrama.png ├── Diagrama.png.bak ├── Diagrama de Classes.asta └── README.md ├── .gitignore ├── Pipfile ├── contrib └── env_gen.py ├── how_to.md ├── manage.py └── README.md /core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /accounts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projectmanager/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /accounts/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /accounts/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from .test_login import LoginTestCase -------------------------------------------------------------------------------- /accounts/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | dj-database-url 2 | django-widget-tweaks 3 | django 4 | python-decouple 5 | pytz -------------------------------------------------------------------------------- /Documentação/Diagrama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marlysson/Project-Manager/HEAD/Documentação/Diagrama.png -------------------------------------------------------------------------------- /core/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html'%} 2 | 3 | {% block content %} 4 | 5 | 6 | {% endblock %} -------------------------------------------------------------------------------- /Documentação/Diagrama.png.bak: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marlysson/Project-Manager/HEAD/Documentação/Diagrama.png.bak -------------------------------------------------------------------------------- /core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | name = 'core' 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | */__pycache__ 2 | */*/__pycache__ 3 | *.sqlite3 4 | .env 5 | .venv 6 | .vscode/* 7 | *.pyc 8 | Pipfile.lock -------------------------------------------------------------------------------- /accounts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountsConfig(AppConfig): 5 | name = 'accounts' 6 | -------------------------------------------------------------------------------- /Documentação/Diagrama de Classes.asta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marlysson/Project-Manager/HEAD/Documentação/Diagrama de Classes.asta -------------------------------------------------------------------------------- /core/forms.py: -------------------------------------------------------------------------------- 1 | from django.forms import ModelForm 2 | from .models import Funcionario 3 | 4 | 5 | class FuncionarioForm(ModelForm): 6 | 7 | class Meta: 8 | model = Funcionario 9 | fields = ["nome", "idade", "salario", "cargo"] 10 | -------------------------------------------------------------------------------- /core/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import * 3 | 4 | 5 | admin.site.register(Projeto) 6 | admin.site.register(Funcionario) 7 | admin.site.register(Equipe) 8 | admin.site.register(Tarefa) 9 | admin.site.register(Item) 10 | admin.site.register(Comentario) 11 | -------------------------------------------------------------------------------- /core/templatetags/format_duration.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | @register.filter 6 | def duration(td): 7 | segundos = int(td.total_seconds()) 8 | horas = segundos // 3600 9 | minutos = (segundos % 3600) // 60 10 | 11 | return '{}h:{}min:{}sec'.format(horas, minutos,segundos) -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | url = "https://pypi.python.org/simple" 4 | verify_ssl = true 5 | name = "pypi" 6 | 7 | 8 | [packages] 9 | 10 | dj-database-url = "*" 11 | django-widget-tweaks = "*" 12 | django = "*" 13 | python-decouple = "*" 14 | pytz = "*" 15 | 16 | 17 | [dev-packages] 18 | 19 | 20 | 21 | [requires] 22 | 23 | python_version = "3.6" 24 | -------------------------------------------------------------------------------- /accounts/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import RegisterView 3 | from django.contrib.auth import views as auth_views 4 | 5 | urlpatterns = [ 6 | path('register/', RegisterView.as_view(), name='register'), 7 | path('login/', auth_views.LoginView.as_view(template_name='accounts/login.html'), name='login'), 8 | path('logout/', auth_views.LogoutView.as_view(), name='logout'), 9 | 10 | ] 11 | -------------------------------------------------------------------------------- /projectmanager/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for projectmanager 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/1.11/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", "projectmanager.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /accounts/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.models import User 3 | 4 | class UserCreationForm(forms.ModelForm): 5 | ''' Cadastro de User ''' 6 | 7 | password = forms.CharField(widget=forms.PasswordInput) 8 | 9 | class Meta: 10 | model = User 11 | fields = ('username', 'first_name', 'email', 'password') 12 | 13 | 14 | class UserLoginForm(forms.ModelForm): 15 | ''' Cadastro de User ''' 16 | 17 | password = forms.CharField(widget=forms.PasswordInput) 18 | 19 | class Meta: 20 | model = User 21 | fields = ('username', 'password') 22 | -------------------------------------------------------------------------------- /accounts/templates/accounts/register.html: -------------------------------------------------------------------------------- 1 | {% extends 'base_login.html' %} 2 | {% load widget_tweaks %} 3 | 4 | {% block content %} 5 |

Cadastro

6 | 7 |
8 | {% csrf_token %} 9 | {% for field in form %} 10 |
11 | 12 | {% render_field field class='form-control' %} 13 |
14 | {% endfor %} 15 | 16 | 17 |
18 | 19 | {% endblock content %} 20 | -------------------------------------------------------------------------------- /accounts/templates/accounts/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base_login.html' %} 2 | {% load widget_tweaks %} 3 | 4 | {% block content %} 5 | 6 |
7 | {% csrf_token %} 8 | {% for field in form %} 9 |
10 | 11 | {% render_field field class='form-control' %} 12 |
13 | {% endfor %} 14 | 15 | 16 | 17 | Registrar-se 18 |
19 | 20 | {% endblock %} -------------------------------------------------------------------------------- /accounts/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib import messages 2 | from django.contrib.auth import authenticate, login 3 | from django.urls import reverse_lazy 4 | from django.shortcuts import redirect 5 | from django.views.generic import CreateView, FormView 6 | from .forms import UserCreationForm, UserLoginForm 7 | 8 | 9 | class RegisterView(CreateView): 10 | template_name = 'accounts/register.html' 11 | form_class = UserCreationForm 12 | 13 | def form_valid(self, form): 14 | user = form.save(commit=False) 15 | user.set_password(form.cleaned_data['password']) 16 | user.save() 17 | 18 | messages.success(self.request, 'Usuário criado com sucesso.') 19 | return redirect(reverse_lazy('login')) -------------------------------------------------------------------------------- /contrib/env_gen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Django SECRET_KEY generator. 5 | """ 6 | from django.utils.crypto import get_random_string 7 | 8 | 9 | chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)' 10 | 11 | CONFIG_STRING = """ 12 | DEBUG=True 13 | SECRET_KEY=%s 14 | ALLOWED_HOSTS=127.0.0.1, .localhost 15 | #DATABASE_URL=postgres://USER:PASSWORD@HOST:PORT/NAME 16 | #DEFAULT_FROM_EMAIL= 17 | #EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend 18 | #EMAIL_HOST= 19 | #EMAIL_PORT= 20 | #EMAIL_USE_TLS= 21 | #EMAIL_HOST_USER= 22 | #EMAIL_HOST_PASSWORD= 23 | """.strip() % get_random_string(50, chars) 24 | 25 | # Writing our configuration file to '.env' 26 | with open('../.env', 'w') as configfile: 27 | configfile.write(CONFIG_STRING) 28 | -------------------------------------------------------------------------------- /accounts/tests/test_login.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.contrib.auth import authenticate 3 | 4 | from django.contrib.auth.models import User 5 | 6 | class LoginTestCase(TestCase): 7 | def setUp(self): 8 | User.objects.create_user('user1', 'user1@domain.com', 'user1pass') 9 | 10 | def test_login_ok(self): 11 | 12 | credentials = {'username': 'user1', 'password': 'user1pass'} 13 | authenticated_user1 = authenticate(**credentials) 14 | 15 | self.assertTrue(authenticated_user1) # user terá o objeto 16 | 17 | def test_login_fail(self): 18 | 19 | credentials = {'username': 'user1', 'password': 'user1wrongpass'} 20 | authenticated_user1 = authenticate(**credentials) 21 | 22 | self.assertFalse(authenticated_user1) # user será None 23 | 24 | -------------------------------------------------------------------------------- /how_to.md: -------------------------------------------------------------------------------- 1 | # Como sincronizar o seu Fork com o repo principal 2 | 3 | * Adicione um remote que aponte para o repositório de origem com o comando: 4 | 5 | ``` 6 | git remote add upstream https://github.com/Marlysson/Project-Manager.git 7 | ``` 8 | * Agora você vai ter um remote chamado `origin` e o recém adicionado `upstream`. Para conferir digite 9 | 10 | ``` 11 | git remote -v 12 | ``` 13 | 14 | * Com o comando a seguir baixe as alterações do remote `upstream`. 15 | 16 | ``` 17 | git fetch upstream 18 | ``` 19 | 20 | * Vá para a branch `master`. 21 | 22 | ``` 23 | git checkout master 24 | ``` 25 | 26 | * Faça o merge da branch `master` com `upstream` no seu repositório local. 27 | 28 | ``` 29 | git merge upstream/master 30 | ``` 31 | 32 | * Envie suas alterações para o seu repositório. 33 | 34 | ``` 35 | git push 36 | ``` 37 | 38 | A partir daí continue suas alterações. Não se esqueça de enviar um Pull Request no final. -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "projectmanager.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /projectmanager/urls.py: -------------------------------------------------------------------------------- 1 | """projectmanager URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.urls import path, include 17 | from django.contrib import admin 18 | 19 | urlpatterns = [ 20 | path('', include('core.urls')), 21 | path('accounts/', include('accounts.urls')), 22 | path('admin/', admin.site.urls), 23 | ] 24 | -------------------------------------------------------------------------------- /core/static/css/estilo.css: -------------------------------------------------------------------------------- 1 | *{ 2 | margin:0; 3 | padding:0; 4 | } 5 | 6 | .title-page{ 7 | margin-bottom:30px; 8 | text-decoration: none; 9 | } 10 | 11 | .row .nav-tabs{ 12 | margin-bottom:20px; 13 | } 14 | 15 | .breadcrumb{ 16 | margin:0; 17 | padding:0; 18 | } 19 | 20 | .pre-requisitos{ 21 | display: none; 22 | } 23 | 24 | .active-pre-requisitos{ 25 | display: block; 26 | } 27 | 28 | .item-input{ 29 | margin-bottom:20px; 30 | } 31 | 32 | .projetos,.funcionarios{ 33 | display: block; 34 | } 35 | 36 | .item-input input[type="checkbox"], .item-input input[type="radio"]{ 37 | display: none; 38 | } 39 | 40 | .item-input input:checked + label{ 41 | background: #398439; 42 | color:white; 43 | } 44 | 45 | .tarefa a{ 46 | color:white; 47 | } 48 | 49 | .tarefa a:hover{ 50 | color:white; 51 | } 52 | 53 | .container_pre_requisitos{ 54 | margin-top:10px; 55 | } 56 | 57 | .container-comentario{ 58 | margin-top:20px; 59 | margin-bottom:20px; 60 | } 61 | 62 | textarea{ 63 | margin-bottom:10px; 64 | } 65 | -------------------------------------------------------------------------------- /core/templates/base_login.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | Project-Manager 7 | 8 | 9 | 10 | {% block css %} 11 | 12 | {% endblock css %} 13 | 14 | 15 |
16 | {% include "includes/messages.html" %} 17 |
18 |
23 | 24 |
25 |

Project Manager

26 |
27 | 28 | {% block content %} 29 | 30 | {% endblock %} 31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /core/templates/includes/messages.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | {% if messages %} 4 | {% for message in messages %} 5 | {% if message.tags %} 6 |
7 | 11 |

12 | 13 | {% if message.tags == 'success' %} 14 | {% trans "Feito! " %} 15 | 16 | {% elif message.tags == 'warning' %} 17 | {% trans "Aviso! " %} 18 | 19 | {% elif message.tags == 'error' %} 20 | {% trans "Erro! " %} 21 | 22 | {% else %} 23 | 24 | {% endif %} 25 | 26 | {{ message }} 27 |

28 |
29 | {% endif %} 30 | {% endfor %} 31 | {% endif %} -------------------------------------------------------------------------------- /core/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path 2 | from . import views 3 | 4 | 5 | tarefa_patterns = [ 6 | path("", views.tarefas, name="tarefas"), 7 | path("", views.tarefa, name="tarefa"), 8 | path( 9 | "/iniciar", 10 | views.iniciar_tarefa, 11 | name="iniciar_tarefa" 12 | ), 13 | path( 14 | "/pausar", 15 | views.pausar_tarefa, 16 | name="pausar_tarefa" 17 | ), 18 | path( 19 | "/concluir", 20 | views.concluir_tarefa, 21 | name="concluir_tarefa" 22 | ), 23 | path( 24 | "/deletar", 25 | views.deletar_tarefa, 26 | name="deletar_tarefa" 27 | ), 28 | path( 29 | "/comentar", 30 | views.comentar, 31 | name="comentar" 32 | ), 33 | path( 34 | "/permitido_iniciar", 35 | views.permissao_iniciar, 36 | name="permissao_iniciar" 37 | ), 38 | ] 39 | 40 | urlpatterns = [ 41 | path("", views.index, name="projetos"), 42 | path("funcionarios/", views.funcionarios, name="funcionarios"), 43 | path("novo_projeto/", views.novo_projeto, name="novo_projeto"), 44 | path("novo_funcionario/", views.novo_funcionario, name="novo_funcionario"), 45 | path("nova_tarefa/", views.nova_tarefa, name="nova_tarefa"), 46 | path("tarefa/", include(tarefa_patterns)), 47 | ] 48 | -------------------------------------------------------------------------------- /core/templates/tarefa.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load format_duration %} 3 | 4 | {% block content %} 5 | 6 |

{{ tarefa.titulo }}

7 | 8 |

{{ tarefa.descricao }}

9 | 10 | 11 | 12 | 24 | 25 | Duração: {{ tarefa.duracao_total|duration }} 26 |
27 | 28 |
29 | 30 | {% csrf_token %} 31 | 32 |
33 |

Adicione um comentário

34 | 35 | 36 | 37 |
38 | 39 |
40 | 41 |
42 | 43 | 44 | {% for comentario in tarefa.comentarios.all %} 45 |
46 |
{{ comentario.criado_por }} comentou à {{ comentario.criado_em|timesince }}
47 |
48 | {{ comentario.conteudo }} 49 |
50 |
51 | {% endfor %} 52 | 53 | {% endblock %} -------------------------------------------------------------------------------- /core/templates/projetos.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 | 5 | {% for projeto in projetos %} 6 | 7 |
8 |
{{ projeto.nome|capfirst }}
9 |
{{ projeto.descricao }}
10 | 17 |
18 | 19 | {% empty %} 20 | 21 |

Ainda não há projetos. Aproveite para criar um.

22 | 23 | {% endfor %} 24 | 25 | 26 | 27 | 28 | 62 | 63 | {% endblock %} -------------------------------------------------------------------------------- /Documentação/README.md: -------------------------------------------------------------------------------- 1 | ## FUNCIONALIDADES GERENCIADOR DE PROJETOS 2 | 3 | ### AUTENTICAÇÃO 4 | 5 | 1. CADASTRO DE USUÁRIO NA PLATAFORMA 6 | 2. LOGIN 7 | 3. RECUPERACAO DE SENHA 8 | 4. ALTERAÇÃO DE SENHA 9 | 10 | ### GERENCIAMENTO DA TAREFA 11 | 12 | 5. CONTAGEM DO TEMPO DA TAREFA 13 | 5.1. QUANTIDADE DE TEMPO DECORRIDO DESDE A ABERTURA ATÉ A CONCLUSÃO, USANDO PAUSA E PLAY NA TAREFA. 14 | 5.2. SE VALENDO PELO HORÁRIO DE TRABALHO DO FUNCIONÁRIO 15 | 16 | 6. ADIÇÃO DE DEPENDÊNCIA ENTRE TAREFAS 17 | 6.1. SE INICIAR UMA TAREFA QUE TEM DEPENDÊNCIAS QUE NÃO SE INICIARAM GERAR UM ERRO 18 | 19 | 7. ADICIONAR RESPONSÁVEL PELA TAREFA 20 | 8. ADICIONAR LABELS DE IMPORTÂNCIA DA TAREFA 21 | 8.1. PODENDO ADICIONAR O STATUS DA TAREFA 22 | 9. DEFINIÇÃO DE PRAZOS DAS TAREFAS 23 | 10. EFETUAR BUSCA POR NOME DAS TAREFAS 24 | 25 | ### GERENCIAMENTO DE NOTIFICAÇÕES 26 | 27 | 11. MAPEAR EVENTOS QUE OCORREM NO SISTEMA 28 | -ALTERAÇÃO DE PRAZO DA TAREFA 29 | -ADIÇÃO DE RESPONSÁVEIS NA TAREFA 30 | -PRAZO DA TAREFA PRÓXIMO DE SE ESGOTAR 31 | -CITAÇÃO EM COMENTÁRIOS 32 | -REAÇÃO À COMENTÁRIOS 33 | 34 | ### TRATAMENTO DE ENGAJAMENTO DOS TIMES NOS PROJETOS ( RELATÓRIO ) 35 | 36 | 13. VISUALIZAÇÃO DE PRODUTIVIDADE DE FUNCIONÁRIO 37 | 12.1. QUANTIDADE DE TAREFAS FEITAS POR FUNCIONÁRIO 38 | 39 | ### GERENCIAMENTO DO FLUXO DE TRABALHO 40 | 41 | 14. CRIAÇÃO DO CONCEITO DE SPRINT E ASSOCIAÇÃO DE ALGUNS REQUISITOS DENTRO DO SPRINT 42 | 15. GERAR RELATÓRIO DE QUANTIDADE DE TAREFAS POR SPRINTS 43 | 15.1. QUANTAS TAREFAS EM CADA SPRINT ATRASARAM 44 | 15.2. QUANTAS TAREFAS TERMINARAM NO TEMPO HÁBIL 45 | 15.3. QUANTIDADE DE TAREFAS POR SPRINT 46 | 47 | ### GERENCIAMENTO DE INTERAÇÃO NA TAREFA ( SIMILAR AO GITHUB ISSUES COMMENTS ) 48 | 49 | 17. POSSIBILIDADE DE COMENTÁRIO NA TAREFA 50 | 17. POSSIBILIDADE DE REAGIR AO COMENTÁRIO 51 | SIMILAR AO FACEBOOK REACTIONS 52 | 18. POSSIBILIDADE DE CITAÇÃO NOS COMENTÁRIOS 53 | E COM ISSO CRIAR UMA NOTIFICAÇÃO 54 | 55 | 56 | ## SEPARAÇÃO DE CONCEITOS NAS APLICAÇÕES 57 | 58 | - AUTH 59 | - NOTIFICATIONS 60 | - WORKFLOW 61 | - THREAD-COMMENTS -------------------------------------------------------------------------------- /core/templates/funcionarios.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 | 5 | {% if funcionarios.count > 0 %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% for funcionario in funcionarios %} 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% endfor %} 24 | 25 |
NomeIdadeCargoSalário
{{ funcionario.nome }}{{ funcionario.idade }}{{ funcionario.cargo }}{{ funcionario.salario }}
26 | 27 | {% else %} 28 | 29 |

Não há funcionarios ainda.

30 | 31 | {% endif %} 32 | 33 | 34 | 35 | 36 | 87 | 88 | {% endblock %} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project Manager 2 | 3 | Description's project: **Implementation of a simple project manager.** 4 | 5 | ## Features 6 | 7 | - Team management 8 | - Tasks ( using checklists ) 9 | - Time management 10 | - Reports 11 | 12 | ## Requirements 13 | 14 | - Python 3.6 15 | - [Pipenv](https://docs.pipenv.org/) 16 | 17 | ## Building the development environment 18 | 19 | 20 | ```bash 21 | # Install dependence's and environment's library 22 | sudo pip install pipenv 23 | ``` 24 | 25 | ```bash 26 | # Clone the repository 27 | git clone https://github.com/Marlysson/Project-Manager.git 28 | 29 | # Enter the project 30 | cd Project-Manager 31 | 32 | # Install dependences 33 | pipenv install 34 | 35 | # Create and load environment variables 36 | python contrib/env_gen.py 37 | 38 | # Activate environment's project 39 | pipenv shell 40 | 41 | # Run migrations 42 | python manage.py migrate 43 | ``` 44 | 45 | ## How synchonize your fork 46 | 47 | Read [how_to](how_to.md) 48 | 49 | ## Inicial class diagram's project 50 | 51 | ![Projeto de classes](https://github.com/Marlysson/Project-Manager/blob/master/Documenta%C3%A7%C3%A3o/Diagrama.png) 52 | 53 | ## FUNCIONALIDADES GERENCIADOR DE PROJETOS 54 | 55 | ### AUTENTICAÇÃO 56 | 57 | 1. CADASTRO DE USUÁRIO NA PLATAFORMA 58 | 2. LOGIN 59 | 3. RECUPERACAO DE SENHA 60 | 4. ALTERAÇÃO DE SENHA 61 | 62 | ### GERENCIAMENTO DA TAREFA 63 | 64 | 5. CONTAGEM DO TEMPO DA TAREFA 65 | 5.1. QUANTIDADE DE TEMPO DECORRIDO DESDE A ABERTURA ATÉ A CONCLUSÃO, USANDO PAUSA E PLAY NA TAREFA. 66 | 5.2. SE VALENDO PELO HORÁRIO DE TRABALHO DO FUNCIONÁRIO 67 | 68 | 6. ADIÇÃO DE DEPENDÊNCIA ENTRE TAREFAS 69 | 6.1. SE INICIAR UMA TAREFA QUE TEM DEPENDÊNCIAS QUE NÃO SE INICIARAM GERAR UM ERRO 70 | 71 | 7. ADICIONAR RESPONSÁVEL PELA TAREFA 72 | 8. ADICIONAR LABELS DE IMPORTÂNCIA DA TAREFA 73 | 8.1. PODENDO ADICIONAR O STATUS DA TAREFA 74 | 9. DEFINIÇÃO DE PRAZOS DAS TAREFAS 75 | 10. EFETUAR BUSCA POR NOME DAS TAREFAS 76 | 77 | ### GERENCIAMENTO DE NOTIFICAÇÕES 78 | 79 | 11. MAPEAR EVENTOS QUE OCORREM NO SISTEMA 80 | -ALTERAÇÃO DE PRAZO DA TAREFA 81 | -ADIÇÃO DE RESPONSÁVEIS NA TAREFA 82 | -PRAZO DA TAREFA PRÓXIMO DE SE ESGOTAR 83 | -CITAÇÃO EM COMENTÁRIOS 84 | -REAÇÃO À COMENTÁRIOS 85 | 86 | ### TRATAMENTO DE ENGAJAMENTO DOS TIMES NOS PROJETOS ( RELATÓRIO ) 87 | 88 | 13. VISUALIZAÇÃO DE PRODUTIVIDADE DE FUNCIONÁRIO 89 | 12.1. QUANTIDADE DE TAREFAS FEITAS POR FUNCIONÁRIO 90 | 91 | ### GERENCIAMENTO DO FLUXO DE TRABALHO 92 | 93 | 14. CRIAÇÃO DO CONCEITO DE SPRINT E ASSOCIAÇÃO DE ALGUNS REQUISITOS DENTRO DO SPRINT 94 | 15. GERAR RELATÓRIO DE QUANTIDADE DE TAREFAS POR SPRINTS 95 | 15.1. QUANTAS TAREFAS EM CADA SPRINT ATRASARAM 96 | 15.2. QUANTAS TAREFAS TERMINARAM NO TEMPO HÁBIL 97 | 15.3. QUANTIDADE DE TAREFAS POR SPRINT 98 | 99 | ### GERENCIAMENTO DE INTERAÇÃO NA TAREFA ( SIMILAR AO GITHUB ISSUES COMMENTS ) 100 | 101 | 17. POSSIBILIDADE DE COMENTÁRIO NA TAREFA 102 | 17. POSSIBILIDADE DE REAGIR AO COMENTÁRIO 103 | SIMILAR AO FACEBOOK REACTIONS 104 | 18. POSSIBILIDADE DE CITAÇÃO NOS COMENTÁRIOS 105 | E COM ISSO CRIAR UMA NOTIFICAÇÃO 106 | 107 | 108 | ## SEPARAÇÃO DE CONCEITOS NAS APLICAÇÕES 109 | 110 | - AUTH 111 | - NOTIFICATIONS 112 | - WORKFLOW 113 | - THREAD-COMMENTS 114 | -------------------------------------------------------------------------------- /core/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | Project-Manager 7 | 8 | 9 | 10 | 11 | 12 |
13 | {% include "includes/messages.html" %} 14 |
15 |
20 | 21 |
22 |

Project Manager

23 |
24 | 25 |
26 | 36 |
37 | 38 | {% block content %} 39 | 40 | {% endblock %} 41 | 42 |
43 | 44 | 45 | 46 | 51 | 52 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /projectmanager/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from decouple import config, Csv 3 | from dj_database_url import parse as dburl 4 | 5 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 6 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 7 | 8 | 9 | # Quick-start development settings - unsuitable for production 10 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 11 | 12 | # SECURITY WARNING: keep the secret key used in production secret! 13 | SECRET_KEY = config('SECRET_KEY') 14 | 15 | # SECURITY WARNING: don't run with debug turned on in production! 16 | DEBUG = config('DEBUG', default=False, cast=bool) 17 | 18 | ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv()) 19 | 20 | 21 | # Application definition 22 | 23 | INSTALLED_APPS = [ 24 | 'django.contrib.admin', 25 | 'django.contrib.auth', 26 | 'django.contrib.contenttypes', 27 | 'django.contrib.sessions', 28 | 'django.contrib.messages', 29 | 'django.contrib.staticfiles', 30 | # Third apps 31 | 'widget_tweaks', 32 | # My apps 33 | 'accounts', 34 | 'core', 35 | ] 36 | 37 | MIDDLEWARE = [ 38 | 'django.middleware.security.SecurityMiddleware', 39 | 'django.contrib.sessions.middleware.SessionMiddleware', 40 | 'django.middleware.common.CommonMiddleware', 41 | 'django.middleware.csrf.CsrfViewMiddleware', 42 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 43 | 'django.contrib.messages.middleware.MessageMiddleware', 44 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 45 | ] 46 | 47 | ROOT_URLCONF = 'projectmanager.urls' 48 | 49 | TEMPLATES = [ 50 | { 51 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 52 | 'DIRS': [], 53 | 'APP_DIRS': True, 54 | 'OPTIONS': { 55 | 'context_processors': [ 56 | 'django.template.context_processors.debug', 57 | 'django.template.context_processors.request', 58 | 'django.contrib.auth.context_processors.auth', 59 | 'django.contrib.messages.context_processors.messages', 60 | ], 61 | }, 62 | }, 63 | ] 64 | 65 | WSGI_APPLICATION = 'projectmanager.wsgi.application' 66 | 67 | 68 | # Database 69 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 70 | 71 | DATABASES = { 72 | 'default': { 73 | 'ENGINE': 'django.db.backends.sqlite3', 74 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 75 | } 76 | } 77 | 78 | 79 | # Password validation 80 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 81 | 82 | AUTH_PASSWORD_VALIDATORS = [ 83 | { 84 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 85 | }, 86 | { 87 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 88 | }, 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 94 | }, 95 | ] 96 | 97 | 98 | # Internationalization 99 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 100 | 101 | LANGUAGE_CODE = 'pt-br' 102 | 103 | TIME_ZONE = 'UTC' 104 | 105 | USE_I18N = True 106 | 107 | USE_L10N = True 108 | 109 | USE_TZ = True 110 | 111 | USE_THOUSAND_SEPARATOR = True 112 | 113 | DECIMAL_SEPARATOR = ',' 114 | 115 | # Static files (CSS, JavaScript, Images) 116 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 117 | 118 | STATIC_URL = '/static/' 119 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') 120 | 121 | LOGIN_URL = 'login' 122 | LOGOUT_REDIRECT_URL = 'login' 123 | LOGIN_REDIRECT_URL = 'projetos' 124 | -------------------------------------------------------------------------------- /core/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib import messages 2 | from django.contrib.auth import authenticate, login as auth_login 3 | from django.contrib.auth.decorators import login_required 4 | from django.contrib.auth.models import User 5 | from django.contrib.auth.views import LogoutView 6 | from django.urls import reverse 7 | from django.http import JsonResponse 8 | from django.shortcuts import render, redirect 9 | from django.views.decorators.http import require_http_methods 10 | from .forms import FuncionarioForm 11 | from .models import Projeto, Funcionario, Tarefa, Comentario 12 | 13 | 14 | @login_required 15 | def index(request): 16 | projetos = Projeto.objects.all() 17 | contexto = {"projetos": projetos} 18 | return render(request, "projetos.html", contexto) 19 | 20 | 21 | @login_required 22 | def novo_projeto(request): 23 | nome = request.POST.get("nome") 24 | descricao = request.POST.get("descricao") 25 | novo = Projeto.objects.create(nome=nome, descricao=descricao) 26 | return redirect("projetos") 27 | 28 | 29 | @login_required 30 | def funcionarios(request): 31 | funcionarios = Funcionario.objects.all() 32 | contexto = {"funcionarios": funcionarios} 33 | return render(request, "funcionarios.html", contexto) 34 | 35 | 36 | @login_required 37 | def novo_funcionario(request): 38 | novo_funcionario = FuncionarioForm(request.POST) 39 | if novo_funcionario.is_valid(): 40 | novo_funcionario.save() 41 | return redirect("funcionarios") 42 | 43 | 44 | @login_required 45 | def tarefas(request): 46 | tarefas = Tarefa.objects.all() 47 | projetos = Projeto.objects.all() 48 | funcionarios = Funcionario.objects.all() 49 | 50 | contexto = { 51 | "tarefas": tarefas, 52 | "projetos": projetos, 53 | "funcionarios": funcionarios 54 | } 55 | 56 | return render(request, "tarefas.html", contexto) 57 | 58 | 59 | @login_required 60 | def tarefa(request, id_tarefa): 61 | tarefa = Tarefa.objects.get(id=id_tarefa) 62 | return render(request, "tarefa.html", {"tarefa": tarefa}) 63 | 64 | 65 | @login_required 66 | def nova_tarefa(request): 67 | if request.method == 'POST': 68 | titulo = request.POST.get("titulo").capitalize() 69 | descricao = request.POST.get("descricao").capitalize() 70 | projeto = request.POST.get("projeto") 71 | responsavel = request.POST.get("funcionario") 72 | pre_requisitos = request.POST.getlist("pre_requisito") 73 | ids_requisitos = list(map(int, pre_requisitos)) 74 | projeto = Projeto.objects.get(id=projeto) 75 | responsavel = Funcionario.objects.get(id=responsavel) 76 | 77 | tarefa = Tarefa( 78 | titulo=titulo, 79 | descricao=descricao, 80 | projeto=projeto, 81 | responsavel=responsavel 82 | ) 83 | tarefa.save() 84 | 85 | if len(ids_requisitos): 86 | for id_requisito in ids_requisitos: 87 | requisito = Tarefa.objects.get(id=id_requisito) 88 | tarefa.pre_requisitos.add(requisito) 89 | 90 | return redirect("tarefas") 91 | 92 | 93 | @require_http_methods(["POST"]) 94 | def comentar(request, id_tarefa): 95 | conteudo = request.POST.get("conteudo") 96 | tarefa = Tarefa.objects.get(id=id_tarefa) 97 | funcionario = get_usuario(request) 98 | Comentario.objects.create( 99 | conteudo=conteudo, 100 | tarefa=tarefa, 101 | criado_por=funcionario 102 | ) 103 | return redirect("tarefa", id_tarefa=id_tarefa) 104 | 105 | 106 | @login_required 107 | def iniciar_tarefa(request, id_tarefa): 108 | tarefa = Tarefa.objects.get(id=id_tarefa) 109 | pre_requisitos = tarefa.pre_requisitos.all() 110 | 111 | if len(pre_requisitos): 112 | for requisito in pre_requisitos: 113 | if requisito.status != 3: 114 | messages.info(request,'Esta tarefa possui pré-requisitos, conclua-os primeiro.') 115 | else: 116 | tarefa.iniciar() 117 | 118 | return redirect("tarefas") 119 | 120 | 121 | def pausar_tarefa(request, id_tarefa): 122 | tarefa = Tarefa.objects.get(id=id_tarefa) 123 | tarefa.pausar() 124 | return redirect("tarefas") 125 | 126 | 127 | def concluir_tarefa(request, id_tarefa): 128 | tarefa = Tarefa.objects.get(id=id_tarefa) 129 | tarefa.concluir() 130 | return redirect("tarefas") 131 | 132 | 133 | def deletar_tarefa(request, id_tarefa): 134 | tarefa = Tarefa.objects.get(id=id_tarefa) 135 | tarefa.delete() 136 | return redirect("tarefas") 137 | 138 | 139 | def permissao_iniciar(request, id_tarefa): 140 | tarefa = Tarefa.objects.get(id=id_tarefa) 141 | return JsonResponse({"permitido": tarefa.permitido_iniciar}) 142 | 143 | 144 | def get_usuario(request): 145 | return Funcionario.objects.get(id=1) 146 | 147 | -------------------------------------------------------------------------------- /core/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | 4 | from datetime import timedelta 5 | 6 | 7 | class Projeto(models.Model): 8 | nome = models.CharField(max_length=50) 9 | descricao = models.TextField() 10 | 11 | def addEquipe(self, equipe): 12 | equipe.projeto = self 13 | equipe.save() 14 | 15 | class Meta: 16 | db_table = "projeto" 17 | 18 | def __str__(self): 19 | return self.nome 20 | 21 | 22 | class Funcionario(models.Model): 23 | nome = models.CharField(max_length=100) 24 | idade = models.IntegerField() 25 | salario = models.DecimalField(max_digits=9, decimal_places=2) 26 | cargo = models.CharField(max_length=100) 27 | 28 | class Meta: 29 | db_table = "funcionario" 30 | 31 | def __str__(self): 32 | return self.nome 33 | 34 | 35 | class Equipe(models.Model): 36 | nome = models.CharField(max_length=50) 37 | projeto = models.ForeignKey( 38 | Projeto, 39 | on_delete=models.CASCADE, 40 | related_name="equipes" 41 | ) 42 | membros = models.ManyToManyField(Funcionario) 43 | 44 | def __str__(self): 45 | return self.nome 46 | 47 | class Meta: 48 | db_table = "equipe" 49 | 50 | 51 | class Tarefa(models.Model): 52 | 53 | STATUS_TAREFA = ( 54 | (0, "ABERTA"), 55 | (1, "TRABALHANDO"), 56 | (2, "PAUSADA"), 57 | (3, "CONCLUíDA") 58 | ) 59 | 60 | titulo = models.CharField(max_length=50) 61 | descricao = models.CharField(max_length=100) 62 | data_conclusao = models.DateTimeField(null=True) 63 | projeto = models.ForeignKey( 64 | Projeto, 65 | on_delete=models.CASCADE, 66 | related_name="tarefas" 67 | ) 68 | horario_de_inicio_atual = models.DateTimeField(null=True) 69 | duracao_total = models.DurationField( 70 | null=True, 71 | default=timedelta(seconds=0) 72 | ) 73 | status = models.CharField(max_length=1, default=0, choices=STATUS_TAREFA) 74 | responsavel = models.ForeignKey(Funcionario, null=True,on_delete=models.SET_NULL) 75 | pre_requisito = models.ManyToManyField( 76 | "self", 77 | symmetrical=False, 78 | related_name="pre_requisitos" 79 | ) 80 | 81 | @property 82 | def is_open(self): 83 | return self.status == '0' 84 | 85 | @property 86 | def is_running(self): 87 | return self.status == '1' 88 | 89 | @property 90 | def is_paused(self): 91 | return self.status == '2' 92 | 93 | @property 94 | def is_done(self): 95 | return self.status == '3' 96 | 97 | def iniciar(self): 98 | 99 | self.horario_de_inicio_atual = timezone.now() 100 | self.status = 1 101 | 102 | self.save() 103 | 104 | def concluir(self): 105 | 106 | self.dataconclusao = timezone.now() 107 | self.duracao_total += timezone.now() - self.dataconclusao 108 | self.status = 3 109 | 110 | self.save() 111 | 112 | def pausar(self): 113 | 114 | diferenca = timezone.now() - self.horario_de_inicio_atual 115 | self.duracao_total += diferenca 116 | 117 | self.status = 2 118 | self.save() 119 | 120 | @property 121 | def permitido_iniciar(self): 122 | 123 | for requisito in self.pre_requisitos: 124 | if requisito.status != 3: 125 | return False 126 | return True 127 | 128 | class Meta: 129 | db_table = "tarefa" 130 | 131 | def __str__(self): 132 | return "{}, {}".format(self.titulo, self.status) 133 | 134 | 135 | class Checklist(models.Model): 136 | descricao = models.CharField(max_length=50) 137 | tarefa = models.ForeignKey( 138 | Tarefa, 139 | on_delete=models.CASCADE, 140 | related_name="checklists" 141 | ) 142 | 143 | def addItem(self, item): 144 | item.checklist = self 145 | item.save() 146 | 147 | class Meta: 148 | db_table = "checklist" 149 | 150 | def __str__(self): 151 | return self.descricao 152 | 153 | 154 | class Item(models.Model): 155 | descricao = models.CharField(max_length=50) 156 | status = models.BooleanField(default=False) 157 | checklist = models.ForeignKey( 158 | Checklist, 159 | on_delete=models.CASCADE, 160 | related_name="itens" 161 | ) 162 | 163 | class Meta: 164 | db_table = "item" 165 | 166 | def __str__(self): 167 | return "{}, {}".format(self.descricao, self.status) 168 | 169 | 170 | class Comentario(models.Model): 171 | 172 | conteudo = models.TextField() 173 | tarefa = models.ForeignKey( 174 | Tarefa, 175 | on_delete=models.CASCADE, 176 | related_name="comentarios" 177 | ) 178 | criado_por = models.ForeignKey(Funcionario, on_delete=models.CASCADE) 179 | criado_em = models.DateTimeField(auto_now=True) 180 | 181 | class Meta: 182 | db_table = "comentario" 183 | ordering = ['-criado_em'] 184 | -------------------------------------------------------------------------------- /core/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.2 on 2018-02-22 02:41 3 | from __future__ import unicode_literals 4 | 5 | import datetime 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Checklist', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('descricao', models.CharField(max_length=50)), 23 | ], 24 | options={ 25 | 'db_table': 'checklist', 26 | }, 27 | ), 28 | migrations.CreateModel( 29 | name='Comentario', 30 | fields=[ 31 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 32 | ('conteudo', models.TextField()), 33 | ('criado_em', models.DateTimeField(auto_now=True)), 34 | ], 35 | options={ 36 | 'db_table': 'comentario', 37 | 'ordering': ['-criado_em'], 38 | }, 39 | ), 40 | migrations.CreateModel( 41 | name='Equipe', 42 | fields=[ 43 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 44 | ('nome', models.CharField(max_length=50)), 45 | ], 46 | options={ 47 | 'db_table': 'equipe', 48 | }, 49 | ), 50 | migrations.CreateModel( 51 | name='Funcionario', 52 | fields=[ 53 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 54 | ('nome', models.CharField(max_length=100)), 55 | ('idade', models.IntegerField()), 56 | ('salario', models.DecimalField(decimal_places=2, max_digits=9)), 57 | ('cargo', models.CharField(max_length=100)), 58 | ], 59 | options={ 60 | 'db_table': 'funcionario', 61 | }, 62 | ), 63 | migrations.CreateModel( 64 | name='Item', 65 | fields=[ 66 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 67 | ('descricao', models.CharField(max_length=50)), 68 | ('status', models.BooleanField(default=False)), 69 | ('checklist', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='itens', to='core.Checklist')), 70 | ], 71 | options={ 72 | 'db_table': 'item', 73 | }, 74 | ), 75 | migrations.CreateModel( 76 | name='Projeto', 77 | fields=[ 78 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 79 | ('nome', models.CharField(max_length=50)), 80 | ('descricao', models.TextField()), 81 | ], 82 | options={ 83 | 'db_table': 'projeto', 84 | }, 85 | ), 86 | migrations.CreateModel( 87 | name='Tarefa', 88 | fields=[ 89 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 90 | ('titulo', models.CharField(max_length=50)), 91 | ('descricao', models.CharField(max_length=100)), 92 | ('data_conclusao', models.DateTimeField(null=True)), 93 | ('horario_de_inicio_atual', models.DateTimeField(null=True)), 94 | ('duracao_total', models.DurationField(default=datetime.timedelta(0), null=True)), 95 | ('status', models.CharField(choices=[(0, 'ABERTA'), (1, 'TRABALHANDO'), (2, 'PAUSADA'), (3, 'CONCLUíDA')], default=0, max_length=1)), 96 | ('pre_requisito', models.ManyToManyField(related_name='pre_requisitos', to='core.Tarefa')), 97 | ('projeto', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tarefas', to='core.Projeto')), 98 | ('responsavel', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='core.Funcionario')), 99 | ], 100 | options={ 101 | 'db_table': 'tarefa', 102 | }, 103 | ), 104 | migrations.AddField( 105 | model_name='equipe', 106 | name='membros', 107 | field=models.ManyToManyField(to='core.Funcionario'), 108 | ), 109 | migrations.AddField( 110 | model_name='equipe', 111 | name='projeto', 112 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='equipes', to='core.Projeto'), 113 | ), 114 | migrations.AddField( 115 | model_name='comentario', 116 | name='criado_por', 117 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.Funcionario'), 118 | ), 119 | migrations.AddField( 120 | model_name='comentario', 121 | name='tarefa', 122 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comentarios', to='core.Tarefa'), 123 | ), 124 | migrations.AddField( 125 | model_name='checklist', 126 | name='tarefa', 127 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='checklists', to='core.Tarefa'), 128 | ), 129 | ] 130 | -------------------------------------------------------------------------------- /core/tests/test_insercoes.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from datetime import timedelta 3 | 4 | from ..models import Projeto, Funcao, Funcionario, \ 5 | Endereco, Tarefa, Checklist, \ 6 | Equipe, Participacao, Item 7 | 8 | class TestInsercoesModelos(TestCase): 9 | 10 | def setUp(self): 11 | self.projeto = Projeto.objects.create(nome="Gateway Pagamentos") 12 | self.tarefa = Tarefa.objects.create(titulo="Implementar Gateway de serviço", 13 | descricao="Lorem ipsum", 14 | esforco=timedelta(days=4), 15 | status=2,projeto=self.projeto) 16 | 17 | self.endereco = Endereco.objects.create(rua="Rua 1",bairro="Bairro 1", 18 | cidade="Cidade 1",estado="Estado 1") 19 | 20 | def test_deve_retornar_quantidade_de_projetos_inseridos(self): 21 | 22 | projeto2 = Projeto(nome="Implementação de API").save() 23 | 24 | quantidade_projetos = Projeto.objects.all().count() 25 | 26 | self.assertEquals(quantidade_projetos,2) 27 | 28 | def test_deve_retornar_quantidade_de_funcoes_criadas(self): 29 | 30 | for funcao in ["Backend Developer","FrontEnd Developer", "Tester"]: 31 | funcao = Funcao.objects.create(nome=funcao) 32 | 33 | funcoes = Funcao.objects.all().count() 34 | 35 | self.assertEquals(funcoes,3) 36 | 37 | def test_deve_criar_um_funcionario_e_associar_um_endereco_a_ele(self): 38 | 39 | funcionario = Funcionario.objects.create(nome="Marlysson",idade=21, 40 | salario=2.500,endereco=self.endereco) 41 | 42 | funcionario_banco = Funcionario.objects.get(id=funcionario.id) 43 | 44 | self.assertEquals(funcionario_banco.nome,"Marlysson") 45 | 46 | def test_criar_uma_checklist_com_itens_para_uma_tarefa(self): 47 | 48 | checklist = Checklist.objects.create(descricao="Primeiros passos para integrar api",tarefa=self.tarefa) 49 | 50 | item1 = Item(descricao="Fazer busca inicial de requisitos") 51 | item2 = Item(descricao="Concluir requisitos prioritários") 52 | item3 = Item(descricao="Enviar para o cliente") 53 | 54 | for item in [item1,item2,item3]: 55 | checklist.addItem(item) 56 | 57 | checklist_inserida = Checklist.objects.get(id=1) 58 | 59 | self.assertEquals(checklist_inserida.descricao,"Primeiros passos para integrar api") 60 | 61 | self.assertEquals(checklist_inserida.itens.count(),3) 62 | 63 | def test_deve_criar_uma_equipe_para_um_projeto_associando_seus_participantes(self): 64 | 65 | equipe = Equipe.objects.create(nome="Desenvolvimento",projeto=self.projeto) 66 | 67 | funcao_backend = Funcao.objects.create(nome="Backend Developer") 68 | funcao_frontend = Funcao.objects.create(nome="FrontEnd Developer") 69 | 70 | funcionario_marlysson = Funcionario.objects.create(nome="Marlysson",idade=21, 71 | salario=2.500,endereco=self.endereco) 72 | 73 | endereco_2 = Endereco.objects.create(rua="Rua 2",bairro="Bairro 2", 74 | cidade="Cidade 2",estado="Estado 2") 75 | 76 | funcionario_marcelo = Funcionario.objects.create(nome="Marcelo",idade=20, 77 | salario=2.600,endereco=endereco_2) 78 | 79 | marlysson = { 80 | "equipe":equipe, 81 | "funcionario":funcionario_marlysson, 82 | "funcao":funcao_backend 83 | } 84 | 85 | marcelo = { 86 | "equipe":equipe, 87 | "funcionario":funcionario_marcelo, 88 | "funcao":funcao_frontend 89 | } 90 | 91 | participacao_1 = Participacao.objects.create(**marlysson) 92 | participacao_2 = Participacao.objects.create(**marcelo) 93 | 94 | equipe = Equipe.objects.get(id=1) 95 | 96 | self.assertEquals(equipe.participantes.count(),2) 97 | 98 | def test_deve_associar_varias_equipes_ao_mesmo_projeto(self): 99 | 100 | marketing = Equipe(nome="Marketing") 101 | desenvolvimento = Equipe(nome="Desenvolvimento") 102 | 103 | self.projeto.addEquipe(marketing) 104 | self.projeto.addEquipe(desenvolvimento) 105 | 106 | projeto = Projeto.objects.get(id=self.projeto.id) 107 | 108 | self.assertEquals(projeto.equipes.count(),2) 109 | 110 | def test_deve_retornar_a_quantidade_de_membros_no_projeto_envolvendo_todas_as_equipes_dele(self): 111 | 112 | equipe = Equipe.objects.create(nome="Desenvolvimento",projeto=self.projeto) 113 | 114 | funcao_backend = Funcao.objects.create(nome="Backend Developer") 115 | funcao_frontend = Funcao.objects.create(nome="FrontEnd Developer") 116 | 117 | funcionario_marlysson = Funcionario.objects.create(nome="Marlysson",idade=21, 118 | salario=2.500,endereco=self.endereco) 119 | 120 | endereco_2 = Endereco.objects.create(rua="Rua 2",bairro="Bairro 2", 121 | cidade="Cidade 2",estado="Estado 2") 122 | 123 | funcionario_marcelo = Funcionario.objects.create(nome="Marcelo",idade=20, 124 | salario=2.600,endereco=endereco_2) 125 | 126 | marlysson = { 127 | "equipe":equipe, 128 | "funcionario":funcionario_marlysson, 129 | "funcao":funcao_backend 130 | } 131 | 132 | marcelo = { 133 | "equipe":equipe, 134 | "funcionario":funcionario_marcelo, 135 | "funcao":funcao_frontend 136 | } 137 | 138 | participacao_1 = Participacao.objects.create(**marlysson) 139 | participacao_2 = Participacao.objects.create(**marcelo) 140 | 141 | participacao = Participacao.objects.filter(equipe__projeto=self.projeto.id).count() 142 | 143 | self.assertEquals(participacao,2) 144 | -------------------------------------------------------------------------------- /core/templates/tarefas.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 | 4 | {% for tarefa in tarefas %} 5 | 6 |
7 | 15 | 16 | 72 | 73 |
74 | 75 | {% empty %} 76 | Não há tarefas ainda. Aproveite e insira algumas. 77 | 78 | {% endfor %} 79 | 80 | 83 | 84 | 187 | 188 | {% endblock %} --------------------------------------------------------------------------------