Merge pull request #444 from taigaio/issue/2851/closed_milestones_errors

Remove 'taiga-info-has-closed-milestones' from userstories resource and added 'taiga-info-total-opened-milestones' and 'taiga-info-total-closed-milestones' to milestones resource
remotes/origin/logger
Alejandro 2015-09-02 11:31:31 +02:00
commit e4369fa09e
7 changed files with 82 additions and 55 deletions

View File

@ -403,6 +403,11 @@ REST_FRAMEWORK = {
"DATETIME_FORMAT": "%Y-%m-%dT%H:%M:%S%z"
}
# Extra expose header related to Taiga APP (see taiga.base.middleware.cors=)
APP_EXTRA_EXPOSE_HEADERS = [
"taiga-info-total-opened-milestones",
"taiga-info-total-closed-milestones"
]
DEFAULT_PROJECT_TEMPLATE = "scrum"
PUBLIC_REGISTER_ENABLED = False

View File

@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django import http
from django.conf import settings
COORS_ALLOWED_ORIGINS = "*"
@ -28,7 +29,7 @@ COORS_EXPOSE_HEADERS = ["x-pagination-count", "x-paginated", "x-paginated-by",
"x-pagination-current", "x-pagination-next", "x-pagination-prev",
"x-site-host", "x-site-register"]
TAIGA_EXPOSE_HEADERS = ["taiga-info-has-closed-milestones"]
COORS_EXTRA_EXPOSE_HEADERS = getattr(settings, "APP_EXTRA_EXPOSE_HEADERS", [])
class CoorsMiddleware(object):
@ -36,7 +37,7 @@ class CoorsMiddleware(object):
response["Access-Control-Allow-Origin"] = COORS_ALLOWED_ORIGINS
response["Access-Control-Allow-Methods"] = ",".join(COORS_ALLOWED_METHODS)
response["Access-Control-Allow-Headers"] = ",".join(COORS_ALLOWED_HEADERS)
response["Access-Control-Expose-Headers"] = ",".join(COORS_EXPOSE_HEADERS + TAIGA_EXPOSE_HEADERS)
response["Access-Control-Expose-Headers"] = ",".join(COORS_EXPOSE_HEADERS + COORS_EXTRA_EXPOSE_HEADERS)
response["Access-Control-Max-Age"] = "3600"
if COORS_ALLOWED_CREDENTIALS:

View File

@ -16,10 +16,28 @@
from django.contrib.contenttypes.models import ContentType
from django.db import transaction
from django.shortcuts import _get_queryset
from . import functions
def get_object_or_none(klass, *args, **kwargs):
"""
Uses get() to return an object, or None if the object does not exist.
klass may be a Model, Manager, or QuerySet object. All other passed
arguments and keyword arguments are used in the get() query.
Note: Like with get(), an MultipleObjectsReturned will be raised if more
than one object is found.
"""
queryset = _get_queryset(klass)
try:
return queryset.get(*args, **kwargs)
except queryset.model.DoesNotExist:
return None
def get_typename_for_model_class(model:object, for_concrete_model=True) -> str:
"""
Get typename for model instance.

View File

@ -14,16 +14,18 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.apps import apps
from taiga.base import filters
from taiga.base import response
from taiga.base.decorators import detail_route
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
from taiga.base.api.utils import get_object_or_404
from taiga.base.utils.db import get_object_or_none
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
from taiga.projects.history.mixins import HistoryResourceMixin
from . import serializers
from . import models
from . import permissions
@ -38,6 +40,26 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudView
filter_fields = ("project", "closed")
queryset = models.Milestone.objects.all()
def list(self, request, *args, **kwargs):
res = super().list(request, *args, **kwargs)
self._add_taiga_info_headers()
return res
def _add_taiga_info_headers(self):
try:
project_id = int(self.request.QUERY_PARAMS.get("project", None))
project_model = apps.get_model("projects", "Project")
project = get_object_or_none(project_model, id=project_id)
except TypeError:
project = None
if project:
opened_milestones = project.milestones.filter(closed=False).count()
closed_milestones = project.milestones.filter(closed=True).count()
self.headers["Taiga-Info-Total-Opened-Milestones"] = opened_milestones
self.headers["Taiga-Info-Total-Closed-Milestones"] = closed_milestones
def get_queryset(self):
qs = super().get_queryset()
qs = self.attach_watchers_attrs_to_queryset(qs)

View File

@ -228,13 +228,6 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
field=order_field)
services.snapshot_userstories_in_bulk(data["bulk_stories"], request.user)
if order_field in ["sprint_order", "backlog_order"]:
# NOTE: This is useful according to issue #2851 to update sprints column in
# the browser client when move USs from the backlog to an sprint, from
# an sprint to the backlog or between sprints.
has_closed_milestones = project.milestones.filter(closed=True).exists()
self.headers["Taiga-Info-Has-Closed-Milestones"] = has_closed_milestones
return response.NoContent()
@list_route(methods=["POST"])

View File

@ -47,3 +47,36 @@ def test_update_milestone_with_userstories_list(client):
client.login(user)
response = client.json.patch(url, json.dumps(form_data))
assert response.status_code == 200
def test_list_milestones_taiga_info_headers(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
role = f.RoleFactory.create(project=project)
f.MembershipFactory.create(project=project, user=user, role=role, is_owner=True)
f.MilestoneFactory.create(project=project, owner=user, closed=True)
f.MilestoneFactory.create(project=project, owner=user, closed=True)
f.MilestoneFactory.create(project=project, owner=user, closed=True)
f.MilestoneFactory.create(project=project, owner=user, closed=False)
f.MilestoneFactory.create(owner=user, closed=False)
url = reverse("milestones-list")
client.login(project.owner)
response1 = client.json.get(url)
response2 = client.json.get(url, {"project": project.id})
assert response1.status_code == 200
assert "taiga-info-total-closed-milestones" in response1["access-control-expose-headers"]
assert "taiga-info-total-opened-milestones" in response1["access-control-expose-headers"]
assert response1.has_header("Taiga-Info-Total-Closed-Milestones") == False
assert response1.has_header("Taiga-Info-Total-Opened-Milestones") == False
assert response2.status_code == 200
assert "taiga-info-total-closed-milestones" in response2["access-control-expose-headers"]
assert "taiga-info-total-opened-milestones" in response2["access-control-expose-headers"]
assert response2.has_header("Taiga-Info-Total-Closed-Milestones") == True
assert response2.has_header("Taiga-Info-Total-Opened-Milestones") == True
assert response2["taiga-info-total-closed-milestones"] == "3"
assert response2["taiga-info-total-opened-milestones"] == "1"

View File

@ -134,51 +134,6 @@ def test_api_update_orders_in_bulk(client):
assert response2.status_code == 204, response2.data
assert response3.status_code == 204, response3.data
def test_api_update_orders_in_bulk_to_test_extra_headers(client):
project = f.create_project()
f.MembershipFactory.create(project=project, user=project.owner, is_owner=True)
us1 = f.create_userstory(project=project)
us2 = f.create_userstory(project=project)
url1 = reverse("userstories-bulk-update-backlog-order")
url2 = reverse("userstories-bulk-update-kanban-order")
url3 = reverse("userstories-bulk-update-sprint-order")
data = {
"project_id": project.id,
"bulk_stories": [{"us_id": us1.id, "order": 1},
{"us_id": us2.id, "order": 2}]
}
client.login(project.owner)
response1 = client.json.post(url1, json.dumps(data))
response2 = client.json.post(url2, json.dumps(data))
response3 = client.json.post(url3, json.dumps(data))
assert response1.status_code == 204
assert response1.has_header("Taiga-Info-Has-Closed-Milestones") == True
assert response1["taiga-info-has-closed-milestones"] == "False"
assert response2.status_code == 204
assert response2.has_header("Taiga-Info-Has-Closed-Milestones") == False
assert response3.status_code == 204
assert response3.has_header("Taiga-Info-Has-Closed-Milestones") == True
assert response3["taiga-info-has-closed-milestones"] == "False"
us1.milestone.closed = True
us1.milestone.save()
response1 = client.json.post(url1, json.dumps(data))
response2 = client.json.post(url2, json.dumps(data))
response3 = client.json.post(url3, json.dumps(data))
assert response1.status_code == 204
assert response1.has_header("Taiga-Info-Has-Closed-Milestones") == True
assert response3["taiga-info-has-closed-milestones"] == "True"
assert response2.status_code == 204
assert response2.has_header("Taiga-Info-Has-Closed-Milestones") == False
assert response3.status_code == 204
assert response3.has_header("Taiga-Info-Has-Closed-Milestones") == True
assert response3["taiga-info-has-closed-milestones"] == "True"
def test_update_userstory_points(client):
user1 = f.UserFactory.create()