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
parent
260e5339c2
commit
70901fd28b
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue