Skip to content
Merged
14 changes: 14 additions & 0 deletions managers/models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
from django.db import models
from django.db.models import Q
from managers.commons.constants import MAX_NAME_LENGTH
from users.models import CustomUser


class ProjectQuerySet(models.QuerySet):
def filter_terminated(self):
return self.filter(terminated=True, stop_date=None)

def filter_active(self):
return self.filter(terminated=False, stop_date=None)

def filter_completed(self):
return self.filter(~Q(stop_date=None))


class Project(models.Model):
name = models.CharField(max_length=MAX_NAME_LENGTH)
start_date = models.DateField()
Expand All @@ -11,5 +23,7 @@ class Project(models.Model):
managers = models.ManyToManyField(CustomUser, related_name="managers")
members = models.ManyToManyField(CustomUser, related_name="members")

objects = ProjectQuerySet.as_manager()

def __str__(self):
return self.name
12 changes: 2 additions & 10 deletions managers/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,7 @@
from managers.models import Project


class ProjectSerializer(serializers.HyperlinkedModelSerializer):
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = (
'url',
'name',
'start_date',
'stop_date',
'terminated',
# 'managers',
# 'members',
)
fields = '__all__'
208 changes: 208 additions & 0 deletions managers/templates/managers/projects_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
{% extends 'base.html' %}
{% load i18n %}
{% block content %}
{% load rest_framework %}

<h1>{% trans "Projects list" %}
<button type="button" class="btn btn-default btn-sm" data-toggle="collapse" data-target="#collapseSort" aria-expanded="false" aria-controls="collapseSort">
<span class="glyphicon glyphicon-th-list"></span>
</button>
</h1>

<div class="collapse" id="collapseSort">
<div class="col-md-4 col-md-offset-4">
<nav id="projects-navbar" class="navbar navbar-light" style="background-color: #e6ecff;">
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link" href="#active" style="color:grey">{% trans "Active" %}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#terminated" style="color:grey">{% trans "Terminated" %}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#completed" style="color:grey">{% trans "Completed" %}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#all" style="color:grey">{% trans "All" %}</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" style="color:grey" role="button" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-sort-by-attributes"></span> </a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{% url 'custom-projects-list' %}?sort=name" style="color:grey">{% trans "Name" %}</a><br>
<a class="dropdown-item" href="{% url 'custom-projects-list' %}?sort=start_date" style="color:grey">{% trans "Start date" %}</a><br>
<a class="dropdown-item" href="{% url 'custom-projects-list' %}?sort=stop_date" style="color:grey">{% trans "Completed date" %}</a><br>
<a class="dropdown-item" href="{% url 'custom-projects-list' %}?sort=members_count" style="color:grey">{% trans "Members count" %}</a><br>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" style="color:grey" role="button" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-sort-by-attributes-alt"></span> </a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{% url 'custom-projects-list' %}?sort=-name" style="color:grey">{% trans "Name" %}</a><br>
<a class="dropdown-item" href="{% url 'custom-projects-list' %}?sort=-start_date" style="color:grey">{% trans "Start date" %}</a><br>
<a class="dropdown-item" href="{% url 'custom-projects-list' %}?sort=-stop_date" style="color:grey">{% trans "Completed date" %}</a><br>
<a class="dropdown-item" href="{% url 'custom-projects-list' %}?sort=-members_count" style="color:grey">{% trans "Members count" %}</a><br>
</div>
</li>
</ul>
</nav>
</div>
</div>

<div data-spy="scroll" data-target="#projects-navbar" data-offset="0">

<div id="active" class="modal-dialog" style="margin-bottom:0">
<button class="btn btn-primary btn-block" type="button" data-toggle="collapse" data-target="#collapseActive" aria-expanded="false" aria-controls="collapseActive">
{% trans "Active" %}
</button>
</div>
<div class="panel-collapse collapse in" id="collapseActive">
<div class="card card-body">
<br/>
<div class="container">
<div class="row">
{% for project in object_list.filter_active %}
<div class="col-sm-4">
<div class="list-group">
<div class="list-group-item list-group-item-action flex-column align-items-start active">
<div class="d-flex w-100 justify-content-between">
<h4 class="mb-1">{{ project.name }}</h4>
</div>
<p class="mb-1">{% trans "started at" %}: {{project.start_date}}</p>
<small>{% trans "managers" %}:
{% for user in project.managers.all %}
{{user.first_name}} {{user.last_name}}<br>
{% endfor %}</small>
<small><span class="glyphicon glyphicon-user"></span> {{project.members.count}}</small>
</div>
</div>
</div>
{% if forloop.counter|divisibleby:3 %}
</div>
<div class="row">
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>

<div id="terminated" class="modal-dialog" style="margin-bottom:0">
<button class="btn btn-info btn-block" type="button" data-toggle="collapse" data-target="#collapseTerminated" aria-expanded="false" aria-controls="collapseTerminated">
{% trans "Terminated" %}
</button>
</div>
<div class="collapse" id="collapseTerminated">
<div class="card card-body">
<br/>
<div class="container">
<div class="row">
{% for project in object_list.filter_terminated %}
<div class="col-sm-4">
<div class="list-group">
<div class="list-group-item list-group-item-info flex-column align-items-start">
<div class="d-flex w-100 justify-content-between">
<h4 class="mb-1">{{ project.name }}</h4>
</div>
<p class="mb-1">{% trans "started at" %}: {{project.start_date}}</p>
<small>{% trans "managers" %}:
{% for user in project.managers.all %}
{{user.first_name}} {{user.last_name}}<br>
{% endfor %}</small>
<small><span class="glyphicon glyphicon-user"></span> {{project.members.count}}</small>
</div>
</div>
</div>
{% if forloop.counter|divisibleby:3 %}
</div>
<div class="row">
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>

<div id="completed" class="modal-dialog" style="margin-bottom:0">
<button class="btn btn-success btn-block" type="button" data-toggle="collapse" data-target="#collapseCompleted" aria-expanded="false" aria-controls="collapseCompleted">
{% trans "Completed" %}
</button>
</div>
<div class="collapse" id="collapseCompleted">
<div class="card card-body">
<br/>
<div class="container">
<div class="row">
{% for project in object_list.filter_completed %}
<div class="col-sm-4">
<div class="list-group">
<div class="list-group-item list-group-item-action list-group-item-success flex-column align-items-start">
<div class="d-flex w-100 justify-content-between">
<h4 class="mb-1">{{ project.name }}</h4>
</div>
<p class="mb-1">{% trans "completed at" %}: {{project.stop_date}}</p>
<small>{% trans "managers" %}:
{% for user in project.managers.all %}
{{user.first_name}} {{user.last_name}}<br>
{% endfor %}</small>
<small><span class="glyphicon glyphicon-user"></span> {{project.members.count}}</small>
</div>
</div>
</div>
{% if forloop.counter|divisibleby:3 %}
</div>
<div class="row">
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>

<div id="all" class="modal-dialog" style="margin-bottom:0">
<button class="btn btn-dark btn-block" type="button" data-toggle="collapse" data-target="#collapseAll" aria-expanded="false" aria-controls="collapseAll">
{% trans "All" %}
</button>
</div>
<div class="collapse" id="collapseAll">
<div class="card card-body">
<br/>
<div class="container">
<div class="row">
{% for project in object_list %}
<div class="col-sm-4">
<div class="list-group">
{% if project.terminated is False and project.stop_date is None %}
<div class="list-group-item list-group-item-action flex-column align-items-start active">
{% elif project.stop_date is not None %}
<div class="list-group-item list-group-item-success flex-column align-items-start">
{% else %}
<div class="list-group-item list-group-item-info flex-column align-items-start">
{% endif %}
<div class="d-flex w-100 justify-content-between">
<h4 class="mb-1">{{ project.name }}</h4>
</div>
{% if project.stop_date is None %}
<p class="mb-1">{% trans "started at" %}: {{project.start_date}}</p>
{% else %}
<p class="mb-1">{% trans "completed at" %}: {{project.stop_date}}</p>
{% endif %}
<small>{% trans "managers" %}:
{% for user in project.managers.all %}
{{user.first_name}} {{user.last_name}}<br>
{% endfor %}</small>
<small><span class="glyphicon glyphicon-user"></span> {{project.members.count}}</small>
</div>
</div>
</div>
{% if forloop.counter|divisibleby:3 %}
</div>
<div class="row">
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>

</div>
{% endblock %}
125 changes: 125 additions & 0 deletions managers/tests/test_unit_managers_apiview.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import datetime

from rest_framework.reverse import reverse
from rest_framework.test import APIRequestFactory

from django.test import TestCase

from managers import views
from managers.models import Project
from users.models import CustomUser


class ProjectTest(TestCase):
def setUp(self):
super().setUp()
self.user = CustomUser(
email='testuser@codepoets.it',
first_name='John',
last_name='Doe',
country='PL',
user_type=CustomUser.UserType.ADMIN.name,
)
self.user.set_password('newuserpasswd')
self.user.full_clean()
self.user.save()

self.project = Project(
name='Example Project',
start_date=datetime.datetime.now().date() - datetime.timedelta(days=30),
stop_date=datetime.datetime.now().date(),
terminated=False,
)
self.project.full_clean()
self.project.save()
self.project.managers.add(self.user)
self.project.members.add(self.user)
self.custom_projects_list_url = [
reverse('custom-projects-list'),
]


class ProjectsListTests(ProjectTest):
def test_project_list_view_should_display_projects_list_on_get(self):
request = APIRequestFactory().get(path=self.custom_projects_list_url[0])
request.user = self.user
response = views.ProjectsList.as_view()(request)
self.assertEqual(response.status_code, 200)
self.assertContains(response, self.project.name)
projects_list = response.context_data['object_list']
self.assertTrue(self.project in projects_list)

def test_projects_list_view_should_show_for_managers_only_own_projects(self):
self.user.user_type = CustomUser.UserType.MANAGER.name
manager_project_list = Project.objects.filter(managers__id=self.user.pk)
request = APIRequestFactory().get(path=self.custom_projects_list_url[0])
request.user = self.user
response = views.ProjectsList.as_view()(request)
self.assertEqual(response.status_code, 200)
self.assertContains(response, self.project.name)
projects_list = response.context_data['object_list']
self.assertEqual(list(manager_project_list), list(projects_list))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it have to be converted to a list?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kbeker Yes, because without that the test returns
AssertionError: <QuerySet [<Project: Example Project>]> != <QuerySet [<Project: Example Project>]>
I don't know why exactly.


def test_project_list_view_should_display_projects_sorted_by_name_ascending(self):
request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + '?sort=name')
request.user = self.user
response = views.ProjectsList.as_view()(request)
projects_list = response.context_data['object_list']
self.assertTrue(projects_list.ordered)
self.assertTrue('name' in projects_list.query.order_by)

def test_project_list_view_should_display_projects_sorted_by_name_descending(self):
request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + '?sort=-name')
request.user = self.user
response = views.ProjectsList.as_view()(request)
projects_list = response.context_data['object_list']
self.assertTrue(projects_list.ordered)
self.assertTrue('-name' in projects_list.query.order_by)

def test_project_list_view_should_display_projects_sorted_by_start_date_ascending(self):
request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + '?sort=start_date')
request.user = self.user
response = views.ProjectsList.as_view()(request)
projects_list = response.context_data['object_list']
self.assertTrue(projects_list.ordered)
self.assertTrue('start_date' in projects_list.query.order_by)

def test_project_list_view_should_display_projects_sorted_by_start_date_descending(self):
request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + '?sort=-start_date')
request.user = self.user
response = views.ProjectsList.as_view()(request)
projects_list = response.context_data['object_list']
self.assertTrue(projects_list.ordered)
self.assertTrue('-start_date' in projects_list.query.order_by)

def test_project_list_view_should_display_projects_sorted_by_stop_date_ascending(self):
request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + '?sort=stop_date')
request.user = self.user
response = views.ProjectsList.as_view()(request)
projects_list = response.context_data['object_list']
self.assertTrue(projects_list.ordered)
self.assertTrue('stop_date' in projects_list.query.order_by)

def test_project_list_view_should_display_projects_sorted_by_stop_date_descending(self):
request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + '?sort=-stop_date')
request.user = self.user
response = views.ProjectsList.as_view()(request)
projects_list = response.context_data['object_list']
self.assertTrue(projects_list.ordered)
self.assertTrue('-stop_date' in projects_list.query.order_by)

def test_project_list_view_should_display_projects_sorted_by_members_count_ascending(self):
request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + '?sort=members_count')
request.user = self.user
response = views.ProjectsList.as_view()(request)
projects_list = response.context_data['object_list']
self.assertTrue(projects_list.ordered)
self.assertTrue('members_count' in projects_list.query.order_by)

def test_project_list_view_should_display_projects_sorted_by_members_count_descending(self):
request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + '?sort=-members_count')
request.user = self.user
response = views.ProjectsList.as_view()(request)
projects_list = response.context_data['object_list']
self.assertTrue(projects_list.ordered)
self.assertTrue('-members_count' in projects_list.query.order_by)
Loading