Moving neighbors logic into standalone module
parent
ac49a76146
commit
8003abbbef
|
@ -63,13 +63,6 @@ class DestroyModelMixin(mixins.DestroyModelMixin):
|
||||||
|
|
||||||
# Other mixins (what they are doing here?)
|
# Other mixins (what they are doing here?)
|
||||||
|
|
||||||
class NeighborsApiMixin(object):
|
|
||||||
def filter_queryset(self, queryset, force=False):
|
|
||||||
for backend in self.get_filter_backends():
|
|
||||||
if force or self.action != "retrieve" or backend not in self.retrieve_exclude_filters:
|
|
||||||
queryset = backend().filter_queryset(self.request, queryset, self)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class PreconditionMixin(object):
|
class PreconditionMixin(object):
|
||||||
def pre_conditions_on_save(self, obj):
|
def pre_conditions_on_save(self, obj):
|
||||||
|
|
|
@ -23,87 +23,3 @@ monkey.patch_api_view()
|
||||||
monkey.patch_serializer()
|
monkey.patch_serializer()
|
||||||
monkey.patch_import_module()
|
monkey.patch_import_module()
|
||||||
monkey.patch_south_hacks()
|
monkey.patch_south_hacks()
|
||||||
|
|
||||||
|
|
||||||
class NeighborsMixin:
|
|
||||||
|
|
||||||
def get_neighbors(self, queryset=None):
|
|
||||||
"""Get the objects around this object.
|
|
||||||
|
|
||||||
:param queryset: A queryset object to use as a starting point. Useful if you need to
|
|
||||||
pre-filter the neighbor candidates.
|
|
||||||
|
|
||||||
:return: The tuple `(previous, next)`.
|
|
||||||
"""
|
|
||||||
if queryset is None:
|
|
||||||
queryset = type(self).objects.get_queryset()
|
|
||||||
queryset = queryset.order_by(*self._get_order_by(queryset))
|
|
||||||
queryset = queryset.filter(~Q(id=self.id))
|
|
||||||
|
|
||||||
return self._get_previous_neighbor(queryset), self._get_next_neighbor(queryset)
|
|
||||||
|
|
||||||
def _get_queryset_order_by(self, queryset):
|
|
||||||
return queryset.query.order_by
|
|
||||||
|
|
||||||
def _get_order_by(self, queryset):
|
|
||||||
return self._get_queryset_order_by(queryset) or self._meta.ordering
|
|
||||||
|
|
||||||
def _get_order_field_value(self, field):
|
|
||||||
field = field.lstrip("-")
|
|
||||||
obj = self
|
|
||||||
for attr in field.split("__"):
|
|
||||||
value = getattr(obj, attr, None)
|
|
||||||
if value is None:
|
|
||||||
break
|
|
||||||
obj = value
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
def _transform_order_field_into_lookup(self, field, operator, operator_if_order_desc):
|
|
||||||
if field.startswith("-"):
|
|
||||||
field = field[1:]
|
|
||||||
operator = operator_if_order_desc
|
|
||||||
return field, operator
|
|
||||||
|
|
||||||
def _format(self, value):
|
|
||||||
if hasattr(value, "format"):
|
|
||||||
value = value.format(obj=self)
|
|
||||||
return value
|
|
||||||
|
|
||||||
def _or(self, conditions):
|
|
||||||
result = Q()
|
|
||||||
for condition in conditions:
|
|
||||||
result |= Q(**{key: self._format(condition[key]) for key in condition})
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _get_neighbor_filters(self, queryset, operator, operator_if_order_desc):
|
|
||||||
conds = []
|
|
||||||
for field in self._get_queryset_order_by(queryset):
|
|
||||||
value = self._get_order_field_value(field)
|
|
||||||
if value is None:
|
|
||||||
continue
|
|
||||||
lookup_field, operator = self._transform_order_field_into_lookup(
|
|
||||||
field, operator, operator_if_order_desc)
|
|
||||||
lookup = "{}__{}".format(lookup_field, operator)
|
|
||||||
conds.append({lookup: value})
|
|
||||||
return conds
|
|
||||||
|
|
||||||
def _get_prev_neighbor_filters(self, queryset):
|
|
||||||
return self._get_neighbor_filters(queryset, "lte", "gte")
|
|
||||||
|
|
||||||
def _get_next_neighbor_filters(self, queryset):
|
|
||||||
return self._get_neighbor_filters(queryset, "gte", "lte")
|
|
||||||
|
|
||||||
def _get_previous_neighbor(self, queryset):
|
|
||||||
queryset = queryset.filter(self._or(self._get_prev_neighbor_filters(queryset)))
|
|
||||||
try:
|
|
||||||
return queryset.reverse()[0]
|
|
||||||
except IndexError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _get_next_neighbor(self, queryset):
|
|
||||||
queryset = queryset.filter(self._or(self._get_next_neighbor_filters(queryset)))
|
|
||||||
try:
|
|
||||||
return queryset[0]
|
|
||||||
except IndexError:
|
|
||||||
return None
|
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
from functools import partial
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
|
||||||
|
Neighbor = namedtuple("Neighbor", "left right")
|
||||||
|
|
||||||
|
|
||||||
|
def disjunction_filters(filters):
|
||||||
|
"""From a list of queryset filters, it returns a disjunction (OR) Q object.
|
||||||
|
|
||||||
|
:param filters: List of filters where each item is a dict where the keys are the lookups and
|
||||||
|
the values the values of those lookups.
|
||||||
|
|
||||||
|
:return: :class:`django.db.models.Q` instance representing the disjunction of the filters.
|
||||||
|
"""
|
||||||
|
result = Q()
|
||||||
|
for filter in filters:
|
||||||
|
result |= Q(**{lookup: value for lookup, value in filter.items()})
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_attribute(obj, attr):
|
||||||
|
"""Finds `attr` in obj.
|
||||||
|
|
||||||
|
:param obj: Object where to look for the attribute.
|
||||||
|
:param attr: Attribute name as a string. It can be a Django lookup field such as
|
||||||
|
`project__owner__name`, in which case it will look for `obj.project.owner.name`.
|
||||||
|
|
||||||
|
:return: The attribute value.
|
||||||
|
:raises: `AttributeError` if some attribute doesn't exist.
|
||||||
|
"""
|
||||||
|
chunks = attr.lstrip("-").split("__")
|
||||||
|
attr, chunks = chunks[0], chunks[1:]
|
||||||
|
obj = value = getattr(obj, attr)
|
||||||
|
for path in chunks:
|
||||||
|
value = getattr(obj, path)
|
||||||
|
obj = value
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def transform_field_into_lookup(name, value, operator="", operator_if_desc=""):
|
||||||
|
"""From a field name and value, return a dict that may be used as a queryset filter.
|
||||||
|
|
||||||
|
:param name: Field name as a string.
|
||||||
|
:param value: Field value.
|
||||||
|
:param operator: Operator to use in the lookup.
|
||||||
|
:param operator_if_desc: If the field is reversed (a "-" in front) use this operator
|
||||||
|
instead.
|
||||||
|
|
||||||
|
:return: A dict that may be used as a queryset filter.
|
||||||
|
"""
|
||||||
|
if name.startswith("-"):
|
||||||
|
name = name[1:]
|
||||||
|
operator = operator_if_desc
|
||||||
|
lookup = "{}{}".format(name, operator)
|
||||||
|
return {lookup: value}
|
||||||
|
|
||||||
|
|
||||||
|
def get_neighbors(obj, results_set=None):
|
||||||
|
"""Get the neighbors of a model instance.
|
||||||
|
|
||||||
|
The neighbors are the objects that are at the left/right of `obj` in the results set.
|
||||||
|
|
||||||
|
:param obj: The object you want to know its neighbors.
|
||||||
|
:param results_set: Find the neighbors applying the constraints of this set (a Django queryset
|
||||||
|
object).
|
||||||
|
|
||||||
|
:return: Tuple `<left neighbor>, <right neighbor>`. Left and right neighbors can be `None`.
|
||||||
|
"""
|
||||||
|
if results_set is None:
|
||||||
|
results_set = type(obj).objects.get_queryset()
|
||||||
|
try:
|
||||||
|
left = _left_candidates(obj, results_set).reverse()[0]
|
||||||
|
except IndexError:
|
||||||
|
left = None
|
||||||
|
try:
|
||||||
|
right = _right_candidates(obj, results_set)[0]
|
||||||
|
except IndexError:
|
||||||
|
right = None
|
||||||
|
|
||||||
|
return Neighbor(left, right)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_candidates(obj, results_set, reverse=False):
|
||||||
|
ordering = (results_set.query.order_by or []) + list(obj._meta.ordering)
|
||||||
|
main_ordering, rest_ordering = ordering[0], ordering[1:]
|
||||||
|
try:
|
||||||
|
filters = obj.get_neighbors_additional_filters(results_set, ordering, reverse)
|
||||||
|
if filters is None:
|
||||||
|
raise AttributeError
|
||||||
|
except AttributeError:
|
||||||
|
filters = [order_field_as_filter(obj, main_ordering, reverse)]
|
||||||
|
filters += [ordering_fields_as_filter(obj, main_ordering, rest_ordering, reverse)]
|
||||||
|
|
||||||
|
return (results_set
|
||||||
|
.filter(~Q(id=obj.id), disjunction_filters(filters))
|
||||||
|
.distinct()
|
||||||
|
.order_by(*ordering))
|
||||||
|
_left_candidates = partial(_get_candidates, reverse=True)
|
||||||
|
_right_candidates = partial(_get_candidates, reverse=False)
|
||||||
|
|
||||||
|
|
||||||
|
def order_field_as_filter(obj, order_field, reverse=None, operator=None):
|
||||||
|
value = get_attribute(obj, order_field)
|
||||||
|
if reverse is not None:
|
||||||
|
if operator is None:
|
||||||
|
operator = ("__gt", "__lt")
|
||||||
|
operator = (operator[1], operator[0]) if reverse else operator
|
||||||
|
else:
|
||||||
|
operator = ()
|
||||||
|
return transform_field_into_lookup(order_field, value, *operator)
|
||||||
|
|
||||||
|
|
||||||
|
def ordering_fields_as_filter(obj, main_order_field, ordering_fields, reverse=False):
|
||||||
|
"""Transform a list of ordering fields into a queryset filter."""
|
||||||
|
filter = order_field_as_filter(obj, main_order_field)
|
||||||
|
for field in ordering_fields:
|
||||||
|
filter.update(order_field_as_filter(obj, field, reverse, ("__gte", "__lte")))
|
||||||
|
return filter
|
|
@ -20,6 +20,7 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from taiga.domains.base import get_active_domain
|
from taiga.domains.base import get_active_domain
|
||||||
from taiga.domains.models import Domain
|
from taiga.domains.models import Domain
|
||||||
|
from .neighbors import get_neighbors
|
||||||
|
|
||||||
|
|
||||||
class PickleField(serializers.WritableField):
|
class PickleField(serializers.WritableField):
|
||||||
|
@ -72,9 +73,12 @@ class NeighborsSerializerMixin:
|
||||||
def get_neighbors(self, obj):
|
def get_neighbors(self, obj):
|
||||||
view, request = self.context.get("view", None), self.context.get("request", None)
|
view, request = self.context.get("view", None), self.context.get("request", None)
|
||||||
if view and request:
|
if view and request:
|
||||||
queryset = view.filter_queryset(view.get_queryset(), True)
|
queryset = view.filter_queryset(view.get_queryset())
|
||||||
previous, next = obj.get_neighbors(queryset)
|
left, right = get_neighbors(obj, results_set=queryset)
|
||||||
|
else:
|
||||||
|
left = right = None
|
||||||
|
|
||||||
return {"previous": self.serialize_neighbor(previous),
|
return {
|
||||||
"next": self.serialize_neighbor(next)}
|
"previous": self.serialize_neighbor(left),
|
||||||
return {"previous": None, "next": None}
|
"next": self.serialize_neighbor(right)
|
||||||
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ from taiga.base import filters
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.base.decorators import list_route
|
from taiga.base.decorators import list_route
|
||||||
from taiga.base.api import ModelCrudViewSet
|
from taiga.base.api import ModelCrudViewSet
|
||||||
from taiga.base.api import NeighborsApiMixin
|
|
||||||
from taiga.projects.mixins.notifications import NotificationSenderMixin
|
from taiga.projects.mixins.notifications import NotificationSenderMixin
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
@ -87,6 +87,7 @@ class IssuesFilter(filters.FilterBackend):
|
||||||
class IssuesOrdering(filters.FilterBackend):
|
class IssuesOrdering(filters.FilterBackend):
|
||||||
def filter_queryset(self, request, queryset, view):
|
def filter_queryset(self, request, queryset, view):
|
||||||
order_by = request.QUERY_PARAMS.get('order_by', None)
|
order_by = request.QUERY_PARAMS.get('order_by', None)
|
||||||
|
|
||||||
if order_by in ['owner', '-owner', 'assigned_to', '-assigned_to']:
|
if order_by in ['owner', '-owner', 'assigned_to', '-assigned_to']:
|
||||||
return queryset.order_by(
|
return queryset.order_by(
|
||||||
'{}__first_name'.format(order_by),
|
'{}__first_name'.format(order_by),
|
||||||
|
@ -95,7 +96,7 @@ class IssuesOrdering(filters.FilterBackend):
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class IssueViewSet(NeighborsApiMixin, NotificationSenderMixin, ModelCrudViewSet):
|
class IssueViewSet(NotificationSenderMixin, ModelCrudViewSet):
|
||||||
model = models.Issue
|
model = models.Issue
|
||||||
queryset = models.Issue.objects.all().prefetch_related("attachments")
|
queryset = models.Issue.objects.all().prefetch_related("attachments")
|
||||||
serializer_class = serializers.IssueNeighborsSerializer
|
serializer_class = serializers.IssueNeighborsSerializer
|
||||||
|
|
|
@ -23,13 +23,12 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from picklefield.fields import PickledObjectField
|
from picklefield.fields import PickledObjectField
|
||||||
|
|
||||||
from taiga.base.models import NeighborsMixin
|
|
||||||
from taiga.base.utils.slug import ref_uniquely
|
from taiga.base.utils.slug import ref_uniquely
|
||||||
from taiga.projects.notifications.models import WatchedMixin
|
from taiga.projects.notifications.models import WatchedMixin
|
||||||
from taiga.projects.mixins.blocked import BlockedMixin
|
from taiga.projects.mixins.blocked import BlockedMixin
|
||||||
|
|
||||||
|
|
||||||
class Issue(NeighborsMixin, WatchedMixin, BlockedMixin, models.Model):
|
class Issue(WatchedMixin, BlockedMixin, models.Model):
|
||||||
ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
|
ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
|
||||||
verbose_name=_("ref"))
|
verbose_name=_("ref"))
|
||||||
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, default=None,
|
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, default=None,
|
||||||
|
@ -77,164 +76,6 @@ class Issue(NeighborsMixin, WatchedMixin, BlockedMixin, models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "({1}) {0}".format(self.ref, self.subject)
|
return "({1}) {0}".format(self.ref, self.subject)
|
||||||
|
|
||||||
def _get_order_by(self, queryset):
|
|
||||||
ordering = self._get_queryset_order_by(queryset)
|
|
||||||
if ordering:
|
|
||||||
main_order = ordering[0]
|
|
||||||
need_extra_ordering = ("severity", "-severity", "owner__first_name",
|
|
||||||
"-owner__first_name", "status", "-status", "priority",
|
|
||||||
"-priority", "assigned_to__first_name",
|
|
||||||
"-assigned_to__first_name")
|
|
||||||
if main_order in need_extra_ordering:
|
|
||||||
ordering += self._meta.ordering
|
|
||||||
|
|
||||||
return ordering
|
|
||||||
|
|
||||||
def _get_prev_neighbor_filters(self, queryset):
|
|
||||||
conds = super()._get_prev_neighbor_filters(queryset)
|
|
||||||
try:
|
|
||||||
main_order = queryset.query.order_by[0]
|
|
||||||
except IndexError:
|
|
||||||
main_order = None
|
|
||||||
|
|
||||||
if main_order == "severity":
|
|
||||||
conds = [{"severity__order__lt": self.severity.order},
|
|
||||||
{"severity__order": self.severity.order,
|
|
||||||
"created_date__lt": self.created_date}]
|
|
||||||
elif main_order == "-severity":
|
|
||||||
conds = [{"severity__order__gt": self.severity.order},
|
|
||||||
{"severity__order": self.severity.order,
|
|
||||||
"created_date__lt": self.created_date}]
|
|
||||||
elif main_order == "status":
|
|
||||||
conds = [{"status__order__lt": self.status.order},
|
|
||||||
{"status__order": self.status.order,
|
|
||||||
"created_date__lt": self.created_date}]
|
|
||||||
elif main_order == "-status":
|
|
||||||
conds = [{"status__order__gt": self.status.order},
|
|
||||||
{"status__order": self.status.order,
|
|
||||||
"created_date__lt": self.created_date}]
|
|
||||||
elif main_order == "priority":
|
|
||||||
conds = [{"priority__order__lt": self.priority.order},
|
|
||||||
{"priority__order": self.priority.order,
|
|
||||||
"created_date__lt": self.created_date}]
|
|
||||||
elif main_order == "-priority":
|
|
||||||
conds = [{"priority__order__gt": self.priority.order},
|
|
||||||
{"priority__order": self.priority.order,
|
|
||||||
"created_date__lt": self.created_date}]
|
|
||||||
elif main_order == "owner__first_name":
|
|
||||||
conds = [{"owner__first_name": self.owner.first_name,
|
|
||||||
"owner__last_name": self.owner.last_name,
|
|
||||||
"created_date__lt": self.created_date},
|
|
||||||
{"owner__first_name": self.owner.first_name,
|
|
||||||
"owner__last_name__lt": self.owner.last_name},
|
|
||||||
{"owner__first_name__lt": self.owner.first_name}]
|
|
||||||
elif main_order == "-owner__first_name":
|
|
||||||
conds = [{"owner__first_name": self.owner.first_name,
|
|
||||||
"owner__last_name": self.owner.last_name,
|
|
||||||
"created_date__lt": self.created_date},
|
|
||||||
{"owner__first_name": self.owner.first_name,
|
|
||||||
"owner__last_name__gt": self.owner.last_name},
|
|
||||||
{"owner__first_name__gt": self.owner.first_name}]
|
|
||||||
elif main_order == "assigned_to__first_name":
|
|
||||||
if self.assigned_to:
|
|
||||||
conds = [{"assigned_to__first_name": self.assigned_to.first_name,
|
|
||||||
"assigned_to__last_name": self.assigned_to.last_name,
|
|
||||||
"created_date__lt": self.created_date},
|
|
||||||
{"assigned_to__first_name": self.assigned_to.first_name,
|
|
||||||
"assigned_to__last_name__lt": self.assigned_to.last_name},
|
|
||||||
{"assigned_to__first_name__lt": self.assigned_to.first_name}]
|
|
||||||
else:
|
|
||||||
conds = [{"assigned_to__isnull": True,
|
|
||||||
"created_date__lt": self.created_date},
|
|
||||||
{"assigned_to__isnull": False}]
|
|
||||||
elif main_order == "-assigned_to__first_name":
|
|
||||||
if self.assigned_to:
|
|
||||||
conds = [{"assigned_to__first_name": self.assigned_to.first_name,
|
|
||||||
"assigned_to__last_name": self.assigned_to.last_name,
|
|
||||||
"created_date__lt": self.created_date},
|
|
||||||
{"assigned_to__first_name": self.assigned_to.first_name,
|
|
||||||
"assigned_to__last_name__gt": self.assigned_to.last_name},
|
|
||||||
{"assigned_to__first_name__gt": self.assigned_to.first_name},
|
|
||||||
{"assigned_to__isnull": True}]
|
|
||||||
else:
|
|
||||||
conds = [{"assigned_to__isnull": True,
|
|
||||||
"created_date__lt": self.created_date},
|
|
||||||
{"assigned_to__isnull": False}]
|
|
||||||
|
|
||||||
return conds
|
|
||||||
|
|
||||||
def _get_next_neighbor_filters(self, queryset):
|
|
||||||
conds = super()._get_next_neighbor_filters(queryset)
|
|
||||||
ordering = queryset.query.order_by
|
|
||||||
try:
|
|
||||||
main_order = ordering[0]
|
|
||||||
except IndexError:
|
|
||||||
main_order = None
|
|
||||||
|
|
||||||
if main_order == "severity":
|
|
||||||
conds = [{"severity__order__gt": self.severity.order},
|
|
||||||
{"severity__order": self.severity.order,
|
|
||||||
"created_date__gt": self.created_date}]
|
|
||||||
elif main_order == "-severity":
|
|
||||||
conds = [{"severity__order__lt": self.severity.order},
|
|
||||||
{"severity__order": self.severity.order,
|
|
||||||
"created_date__gt": self.created_date}]
|
|
||||||
elif main_order == "status":
|
|
||||||
conds = [{"status__order__gt": self.status.order},
|
|
||||||
{"status__order": self.status.order,
|
|
||||||
"created_date__gt": self.created_date}]
|
|
||||||
elif main_order == "-status":
|
|
||||||
conds = [{"status__order__lt": self.status.order},
|
|
||||||
{"status__order": self.status.order,
|
|
||||||
"created_date__gt": self.created_date}]
|
|
||||||
elif main_order == "priority":
|
|
||||||
conds = [{"priority__order__gt": self.priority.order},
|
|
||||||
{"priority__order": self.priority.order,
|
|
||||||
"created_date__gt": self.created_date}]
|
|
||||||
elif main_order == "-priority":
|
|
||||||
conds = [{"priority__order__lt": self.priority.order},
|
|
||||||
{"priority__order": self.priority.order,
|
|
||||||
"created_date__gt": self.created_date}]
|
|
||||||
elif main_order == "owner__first_name":
|
|
||||||
conds = [{"owner__first_name": self.owner.first_name,
|
|
||||||
"owner__last_name": self.owner.last_name,
|
|
||||||
"created_date__gt": self.created_date},
|
|
||||||
{"owner__first_name": self.owner.first_name,
|
|
||||||
"owner__last_name__gt": self.owner.last_name},
|
|
||||||
{"owner__first_name__gt": self.owner.first_name}]
|
|
||||||
elif main_order == "-owner__first_name":
|
|
||||||
conds = [{"owner__first_name": self.owner.first_name,
|
|
||||||
"owner__last_name": self.owner.last_name,
|
|
||||||
"created_date__gt": self.created_date},
|
|
||||||
{"owner__first_name": self.owner.first_name,
|
|
||||||
"owner__last_name__lt": self.owner.last_name},
|
|
||||||
{"owner__first_name__lt": self.owner.first_name}]
|
|
||||||
elif main_order == "assigned_to__first_name":
|
|
||||||
if self.assigned_to:
|
|
||||||
conds = [{"assigned_to__first_name": self.assigned_to.first_name,
|
|
||||||
"assigned_to__last_name": self.assigned_to.last_name,
|
|
||||||
"created_date__gt": self.created_date},
|
|
||||||
{"assigned_to__first_name": self.assigned_to.first_name,
|
|
||||||
"assigned_to__last_name__gt": self.assigned_to.last_name},
|
|
||||||
{"assigned_to__first_name__gt": self.assigned_to.first_name},
|
|
||||||
{"assigned_to__isnull": True}]
|
|
||||||
else:
|
|
||||||
conds = [{"assigned_to__isnull": True,
|
|
||||||
"created_date__gt": self.created_date}]
|
|
||||||
elif main_order == "-assigned_to__first_name" and self.assigned_to:
|
|
||||||
conds = [{"assigned_to__first_name": self.assigned_to.first_name,
|
|
||||||
"assigned_to__last_name": self.assigned_to.last_name,
|
|
||||||
"created_date__gt": self.created_date},
|
|
||||||
{"assigned_to__first_name": self.assigned_to.first_name,
|
|
||||||
"assigned_to__last_name__lt": self.assigned_to.last_name},
|
|
||||||
{"assigned_to__first_name__lt": self.assigned_to.first_name}]
|
|
||||||
else:
|
|
||||||
conds = [{"assigned_to__isnull": True,
|
|
||||||
"created_date__gt": self.created_date},
|
|
||||||
{"assigned_to__isnull": False}]
|
|
||||||
|
|
||||||
return conds
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_closed(self):
|
def is_closed(self):
|
||||||
return self.status.is_closed
|
return self.status.is_closed
|
||||||
|
|
|
@ -28,7 +28,7 @@ from taiga.base.decorators import list_route
|
||||||
from taiga.base.decorators import action
|
from taiga.base.decorators import action
|
||||||
from taiga.base.permissions import has_project_perm
|
from taiga.base.permissions import has_project_perm
|
||||||
from taiga.base.api import ModelCrudViewSet
|
from taiga.base.api import ModelCrudViewSet
|
||||||
from taiga.base.api import NeighborsApiMixin
|
|
||||||
from taiga.projects.mixins.notifications import NotificationSenderMixin
|
from taiga.projects.mixins.notifications import NotificationSenderMixin
|
||||||
from taiga.projects.models import Project
|
from taiga.projects.models import Project
|
||||||
from taiga.projects.history.services import take_snapshot
|
from taiga.projects.history.services import take_snapshot
|
||||||
|
@ -39,7 +39,7 @@ from . import serializers
|
||||||
from . import services
|
from . import services
|
||||||
|
|
||||||
|
|
||||||
class UserStoryViewSet(NeighborsApiMixin, NotificationSenderMixin, ModelCrudViewSet):
|
class UserStoryViewSet(NotificationSenderMixin, ModelCrudViewSet):
|
||||||
model = models.UserStory
|
model = models.UserStory
|
||||||
serializer_class = serializers.UserStoryNeighborsSerializer
|
serializer_class = serializers.UserStoryNeighborsSerializer
|
||||||
list_serializer_class = serializers.UserStorySerializer
|
list_serializer_class = serializers.UserStorySerializer
|
||||||
|
|
|
@ -22,7 +22,6 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from picklefield.fields import PickledObjectField
|
from picklefield.fields import PickledObjectField
|
||||||
|
|
||||||
from taiga.base.models import NeighborsMixin
|
|
||||||
from taiga.base.utils.slug import ref_uniquely
|
from taiga.base.utils.slug import ref_uniquely
|
||||||
from taiga.projects.notifications.models import WatchedMixin
|
from taiga.projects.notifications.models import WatchedMixin
|
||||||
from taiga.projects.mixins.blocked import BlockedMixin
|
from taiga.projects.mixins.blocked import BlockedMixin
|
||||||
|
@ -52,7 +51,8 @@ class RolePoints(models.Model):
|
||||||
return "{}: {}".format(self.role.name, self.points.name)
|
return "{}: {}".format(self.role.name, self.points.name)
|
||||||
|
|
||||||
|
|
||||||
class UserStory(NeighborsMixin, WatchedMixin, BlockedMixin, models.Model):
|
class UserStory(WatchedMixin, BlockedMixin, models.Model):
|
||||||
|
|
||||||
ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
|
ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
|
||||||
verbose_name=_("ref"))
|
verbose_name=_("ref"))
|
||||||
milestone = models.ForeignKey("milestones.Milestone", null=True, blank=True,
|
milestone = models.ForeignKey("milestones.Milestone", null=True, blank=True,
|
||||||
|
@ -112,16 +112,6 @@ class UserStory(NeighborsMixin, WatchedMixin, BlockedMixin, models.Model):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<UserStory %s>" % (self.id)
|
return "<UserStory %s>" % (self.id)
|
||||||
|
|
||||||
def _get_prev_neighbor_filters(self, queryset):
|
|
||||||
conds = [{"order__lt": "{obj.order}"},
|
|
||||||
{"order__lte": "{obj.order}", "ref__lt": "{obj.ref}"}]
|
|
||||||
return conds
|
|
||||||
|
|
||||||
def _get_next_neighbor_filters(self, queryset):
|
|
||||||
conds = [{"order__gt": "{obj.order}"},
|
|
||||||
{"order__gte": "{obj.order}", "ref__gt": "{obj.ref}"}]
|
|
||||||
return conds
|
|
||||||
|
|
||||||
def get_role_points(self):
|
def get_role_points(self):
|
||||||
return self.role_points
|
return self.role_points
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,9 @@ from django.conf import settings
|
||||||
|
|
||||||
import taiga.domains.models
|
import taiga.domains.models
|
||||||
import taiga.projects.models
|
import taiga.projects.models
|
||||||
|
import taiga.projects.userstories.models
|
||||||
|
import taiga.projects.issues.models
|
||||||
|
import taiga.projects.milestones.models
|
||||||
import taiga.users.models
|
import taiga.users.models
|
||||||
import taiga.userstorage.models
|
import taiga.userstorage.models
|
||||||
|
|
||||||
|
@ -76,3 +79,61 @@ class StorageEntryFactory(factory.DjangoModelFactory):
|
||||||
owner = factory.SubFactory("tests.factories.UserFactory")
|
owner = factory.SubFactory("tests.factories.UserFactory")
|
||||||
key = factory.Sequence(lambda n: "key-{}".format(n))
|
key = factory.Sequence(lambda n: "key-{}".format(n))
|
||||||
value = factory.Sequence(lambda n: "value {}".format(n))
|
value = factory.Sequence(lambda n: "value {}".format(n))
|
||||||
|
|
||||||
|
|
||||||
|
class UserStoryFactory(factory.DjangoModelFactory):
|
||||||
|
FACTORY_FOR = taiga.projects.userstories.models.UserStory
|
||||||
|
|
||||||
|
ref = factory.Sequence(lambda n: n)
|
||||||
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
owner = factory.SubFactory("tests.factories.UserFactory")
|
||||||
|
subject = factory.Sequence(lambda n: "User Story {}".format(n))
|
||||||
|
|
||||||
|
|
||||||
|
class MilestoneFactory(factory.DjangoModelFactory):
|
||||||
|
FACTORY_FOR = taiga.projects.milestones.models.Milestone
|
||||||
|
|
||||||
|
name = factory.Sequence(lambda n: "Milestone {}".format(n))
|
||||||
|
owner = factory.SubFactory("tests.factories.UserFactory")
|
||||||
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
|
||||||
|
|
||||||
|
class IssueFactory(factory.DjangoModelFactory):
|
||||||
|
FACTORY_FOR = taiga.projects.issues.models.Issue
|
||||||
|
|
||||||
|
subject = factory.Sequence(lambda n: "Issue {}".format(n))
|
||||||
|
owner = factory.SubFactory("tests.factories.UserFactory")
|
||||||
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
status = factory.SubFactory("tests.factories.IssueStatusFactory")
|
||||||
|
severity = factory.SubFactory("tests.factories.SeverityFactory")
|
||||||
|
priority = factory.SubFactory("tests.factories.PriorityFactory")
|
||||||
|
type = factory.SubFactory("tests.factories.IssueTypeFactory")
|
||||||
|
milestone = factory.SubFactory("tests.factories.MilestoneFactory")
|
||||||
|
|
||||||
|
|
||||||
|
class IssueStatusFactory(factory.DjangoModelFactory):
|
||||||
|
FACTORY_FOR = taiga.projects.models.IssueStatus
|
||||||
|
|
||||||
|
name = factory.Sequence(lambda n: "Issue Status {}".format(n))
|
||||||
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
|
||||||
|
|
||||||
|
class SeverityFactory(factory.DjangoModelFactory):
|
||||||
|
FACTORY_FOR = taiga.projects.models.Severity
|
||||||
|
|
||||||
|
name = factory.Sequence(lambda n: "Severity {}".format(n))
|
||||||
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
|
||||||
|
|
||||||
|
class PriorityFactory(factory.DjangoModelFactory):
|
||||||
|
FACTORY_FOR = taiga.projects.models.Priority
|
||||||
|
|
||||||
|
name = factory.Sequence(lambda n: "Priority {}".format(n))
|
||||||
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
|
||||||
|
|
||||||
|
class IssueTypeFactory(factory.DjangoModelFactory):
|
||||||
|
FACTORY_FOR = taiga.projects.models.IssueType
|
||||||
|
|
||||||
|
name = factory.Sequence(lambda n: "Issue Type {}".format(n))
|
||||||
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
|
|
@ -0,0 +1,304 @@
|
||||||
|
from functools import partial
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from taiga.projects.userstories.models import UserStory
|
||||||
|
from taiga.projects.issues.models import Issue
|
||||||
|
from taiga.base.utils.db import filter_by_tags
|
||||||
|
from taiga.base import neighbors as n
|
||||||
|
from .. import factories as f
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetAttribute:
|
||||||
|
def test_no_attribute(self, object):
|
||||||
|
object.first_name = "name"
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
n.get_attribute(object, "name")
|
||||||
|
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
n.get_attribute(object, "first_name__last_name")
|
||||||
|
|
||||||
|
def test_one_level(self, object):
|
||||||
|
object.name = "name"
|
||||||
|
assert n.get_attribute(object, "name") == object.name
|
||||||
|
|
||||||
|
def test_two_levels(self, object):
|
||||||
|
object.name = object
|
||||||
|
object.name.first_name = "first name"
|
||||||
|
assert n.get_attribute(object, "name__first_name") == object.name.first_name
|
||||||
|
|
||||||
|
def test_three_levels(self, object):
|
||||||
|
object.info = object
|
||||||
|
object.info.name = object
|
||||||
|
object.info.name.first_name = "first name"
|
||||||
|
assert n.get_attribute(object, "info__name__first_name") == object.info.name.first_name
|
||||||
|
|
||||||
|
|
||||||
|
def test_transform_field_into_lookup():
|
||||||
|
transform = partial(n.transform_field_into_lookup, value="chuck", operator="__lt",
|
||||||
|
operator_if_desc="__gt")
|
||||||
|
|
||||||
|
assert transform(name="name") == {"name__lt": "chuck"}
|
||||||
|
assert transform(name="-name") == {"name__gt": "chuck"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_disjunction_filters():
|
||||||
|
filters = [{"age__lt": 21, "name__eq": "chuck"}]
|
||||||
|
result_str = str(n.disjunction_filters(filters))
|
||||||
|
|
||||||
|
assert result_str.startswith("(OR: ")
|
||||||
|
assert "('age__lt', 21)" in result_str
|
||||||
|
assert "('name__eq', 'chuck')" in result_str
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.slow
|
||||||
|
class TestUserStories:
|
||||||
|
def test_no_filters(self):
|
||||||
|
project = f.ProjectFactory.create()
|
||||||
|
|
||||||
|
us1 = f.UserStoryFactory.create(project=project)
|
||||||
|
us2 = f.UserStoryFactory.create(project=project)
|
||||||
|
us3 = f.UserStoryFactory.create(project=project)
|
||||||
|
|
||||||
|
neighbors = n.get_neighbors(us2)
|
||||||
|
|
||||||
|
assert neighbors.left == us1
|
||||||
|
assert neighbors.right == us3
|
||||||
|
|
||||||
|
def test_filtered_by_tags(self):
|
||||||
|
tags = ["test"]
|
||||||
|
project = f.ProjectFactory.create()
|
||||||
|
|
||||||
|
f.UserStoryFactory.create(project=project)
|
||||||
|
us1 = f.UserStoryFactory.create(project=project, tags=tags)
|
||||||
|
us2 = f.UserStoryFactory.create(project=project, tags=tags)
|
||||||
|
|
||||||
|
test_user_stories = filter_by_tags(tags, queryset=UserStory.objects.get_queryset())
|
||||||
|
|
||||||
|
neighbors = n.get_neighbors(us1, results_set=test_user_stories)
|
||||||
|
|
||||||
|
assert neighbors.left is None
|
||||||
|
assert neighbors.right == us2
|
||||||
|
|
||||||
|
def test_filtered_by_milestone(self):
|
||||||
|
project = f.ProjectFactory.create()
|
||||||
|
milestone = f.MilestoneFactory.create(project=project)
|
||||||
|
|
||||||
|
f.UserStoryFactory.create(project=project)
|
||||||
|
us1 = f.UserStoryFactory.create(project=project, milestone=milestone)
|
||||||
|
us2 = f.UserStoryFactory.create(project=project, milestone=milestone)
|
||||||
|
|
||||||
|
milestone_user_stories = UserStory.objects.filter(milestone=milestone)
|
||||||
|
|
||||||
|
neighbors = n.get_neighbors(us1, results_set=milestone_user_stories)
|
||||||
|
|
||||||
|
assert neighbors.left is None
|
||||||
|
assert neighbors.right == us2
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.slow
|
||||||
|
class TestIssues:
|
||||||
|
def test_no_filters(self):
|
||||||
|
project = f.ProjectFactory.create()
|
||||||
|
|
||||||
|
issue1 = f.IssueFactory.create(project=project)
|
||||||
|
issue2 = f.IssueFactory.create(project=project)
|
||||||
|
issue3 = f.IssueFactory.create(project=project)
|
||||||
|
|
||||||
|
neighbors = n.get_neighbors(issue2)
|
||||||
|
|
||||||
|
assert neighbors.left == issue1
|
||||||
|
assert neighbors.right == issue3
|
||||||
|
|
||||||
|
def test_ordering_by_severity(self):
|
||||||
|
project = f.ProjectFactory.create()
|
||||||
|
severity1 = f.SeverityFactory.create(project=project, order=1)
|
||||||
|
severity2 = f.SeverityFactory.create(project=project, order=2)
|
||||||
|
|
||||||
|
issue1 = f.IssueFactory.create(project=project, severity=severity2)
|
||||||
|
issue2 = f.IssueFactory.create(project=project, severity=severity1)
|
||||||
|
issue3 = f.IssueFactory.create(project=project, severity=severity1)
|
||||||
|
|
||||||
|
issues = Issue.objects.filter(project=project).order_by("severity")
|
||||||
|
|
||||||
|
issue2_neighbors = n.get_neighbors(issue2, results_set=issues)
|
||||||
|
issue3_neighbors = n.get_neighbors(issue3, results_set=issues)
|
||||||
|
|
||||||
|
assert issue2_neighbors.left is None
|
||||||
|
assert issue2_neighbors.right == issue3
|
||||||
|
assert issue3_neighbors.left == issue2
|
||||||
|
assert issue3_neighbors.right == issue1
|
||||||
|
|
||||||
|
def test_ordering_by_severity_desc(self):
|
||||||
|
project = f.ProjectFactory.create()
|
||||||
|
severity1 = f.SeverityFactory.create(project=project, order=1)
|
||||||
|
severity2 = f.SeverityFactory.create(project=project, order=2)
|
||||||
|
|
||||||
|
issue1 = f.IssueFactory.create(project=project, severity=severity2)
|
||||||
|
issue2 = f.IssueFactory.create(project=project, severity=severity1)
|
||||||
|
issue3 = f.IssueFactory.create(project=project, severity=severity1)
|
||||||
|
|
||||||
|
issues = Issue.objects.filter(project=project).order_by("-severity")
|
||||||
|
|
||||||
|
issue1_neighbors = n.get_neighbors(issue1, results_set=issues)
|
||||||
|
issue3_neighbors = n.get_neighbors(issue3, results_set=issues)
|
||||||
|
|
||||||
|
assert issue1_neighbors.left is None
|
||||||
|
assert issue1_neighbors.right == issue2
|
||||||
|
assert issue3_neighbors.left == issue2
|
||||||
|
assert issue3_neighbors.right is None
|
||||||
|
|
||||||
|
def test_ordering_by_status(self):
|
||||||
|
project = f.ProjectFactory.create()
|
||||||
|
status1 = f.IssueStatusFactory.create(project=project, order=1)
|
||||||
|
status2 = f.IssueStatusFactory.create(project=project, order=2)
|
||||||
|
|
||||||
|
issue1 = f.IssueFactory.create(project=project, status=status2)
|
||||||
|
issue2 = f.IssueFactory.create(project=project, status=status1)
|
||||||
|
issue3 = f.IssueFactory.create(project=project, status=status1)
|
||||||
|
|
||||||
|
issues = Issue.objects.filter(project=project).order_by("status")
|
||||||
|
|
||||||
|
issue2_neighbors = n.get_neighbors(issue2, results_set=issues)
|
||||||
|
issue3_neighbors = n.get_neighbors(issue3, results_set=issues)
|
||||||
|
|
||||||
|
assert issue2_neighbors.left is None
|
||||||
|
assert issue2_neighbors.right == issue3
|
||||||
|
assert issue3_neighbors.left == issue2
|
||||||
|
assert issue3_neighbors.right == issue1
|
||||||
|
|
||||||
|
def test_ordering_by_status_desc(self):
|
||||||
|
project = f.ProjectFactory.create()
|
||||||
|
status1 = f.IssueStatusFactory.create(project=project, order=1)
|
||||||
|
status2 = f.IssueStatusFactory.create(project=project, order=2)
|
||||||
|
|
||||||
|
issue1 = f.IssueFactory.create(project=project, status=status2)
|
||||||
|
issue2 = f.IssueFactory.create(project=project, status=status1)
|
||||||
|
issue3 = f.IssueFactory.create(project=project, status=status1)
|
||||||
|
|
||||||
|
issues = Issue.objects.filter(project=project).order_by("-status")
|
||||||
|
|
||||||
|
issue1_neighbors = n.get_neighbors(issue1, results_set=issues)
|
||||||
|
issue3_neighbors = n.get_neighbors(issue3, results_set=issues)
|
||||||
|
|
||||||
|
assert issue1_neighbors.left is None
|
||||||
|
assert issue1_neighbors.right == issue2
|
||||||
|
assert issue3_neighbors.left == issue2
|
||||||
|
assert issue3_neighbors.right is None
|
||||||
|
|
||||||
|
def test_ordering_by_priority(self):
|
||||||
|
project = f.ProjectFactory.create()
|
||||||
|
priority1 = f.PriorityFactory.create(project=project, order=1)
|
||||||
|
priority2 = f.PriorityFactory.create(project=project, order=2)
|
||||||
|
|
||||||
|
issue1 = f.IssueFactory.create(project=project, priority=priority2)
|
||||||
|
issue2 = f.IssueFactory.create(project=project, priority=priority1)
|
||||||
|
issue3 = f.IssueFactory.create(project=project, priority=priority1)
|
||||||
|
|
||||||
|
issues = Issue.objects.filter(project=project).order_by("priority")
|
||||||
|
|
||||||
|
issue2_neighbors = n.get_neighbors(issue2, results_set=issues)
|
||||||
|
issue3_neighbors = n.get_neighbors(issue3, results_set=issues)
|
||||||
|
|
||||||
|
assert issue2_neighbors.left is None
|
||||||
|
assert issue2_neighbors.right == issue3
|
||||||
|
assert issue3_neighbors.left == issue2
|
||||||
|
assert issue3_neighbors.right == issue1
|
||||||
|
|
||||||
|
def test_ordering_by_priority_desc(self):
|
||||||
|
project = f.ProjectFactory.create()
|
||||||
|
priority1 = f.PriorityFactory.create(project=project, order=1)
|
||||||
|
priority2 = f.PriorityFactory.create(project=project, order=2)
|
||||||
|
|
||||||
|
issue1 = f.IssueFactory.create(project=project, priority=priority2)
|
||||||
|
issue2 = f.IssueFactory.create(project=project, priority=priority1)
|
||||||
|
issue3 = f.IssueFactory.create(project=project, priority=priority1)
|
||||||
|
|
||||||
|
issues = Issue.objects.filter(project=project).order_by("-priority")
|
||||||
|
|
||||||
|
issue1_neighbors = n.get_neighbors(issue1, results_set=issues)
|
||||||
|
issue3_neighbors = n.get_neighbors(issue3, results_set=issues)
|
||||||
|
|
||||||
|
assert issue1_neighbors.left is None
|
||||||
|
assert issue1_neighbors.right == issue2
|
||||||
|
assert issue3_neighbors.left == issue2
|
||||||
|
assert issue3_neighbors.right is None
|
||||||
|
|
||||||
|
def test_ordering_by_owner(self):
|
||||||
|
project = f.ProjectFactory.create()
|
||||||
|
owner1 = f.UserFactory.create(first_name="Chuck", last_name="Norris")
|
||||||
|
owner2 = f.UserFactory.create(first_name="George", last_name="Of The Jungle")
|
||||||
|
|
||||||
|
issue1 = f.IssueFactory.create(project=project, owner=owner2)
|
||||||
|
issue2 = f.IssueFactory.create(project=project, owner=owner1)
|
||||||
|
issue3 = f.IssueFactory.create(project=project, owner=owner1)
|
||||||
|
|
||||||
|
issues = Issue.objects.filter(project=project).order_by("owner__first_name")
|
||||||
|
|
||||||
|
issue2_neighbors = n.get_neighbors(issue2, results_set=issues)
|
||||||
|
issue3_neighbors = n.get_neighbors(issue3, results_set=issues)
|
||||||
|
|
||||||
|
assert issue2_neighbors.left is None
|
||||||
|
assert issue2_neighbors.right == issue3
|
||||||
|
assert issue3_neighbors.left == issue2
|
||||||
|
assert issue3_neighbors.right == issue1
|
||||||
|
|
||||||
|
def test_ordering_by_owner_desc(self):
|
||||||
|
project = f.ProjectFactory.create()
|
||||||
|
owner1 = f.UserFactory.create(first_name="Chuck", last_name="Norris")
|
||||||
|
owner2 = f.UserFactory.create(first_name="George", last_name="Of The Jungle")
|
||||||
|
|
||||||
|
issue1 = f.IssueFactory.create(project=project, owner=owner2)
|
||||||
|
issue2 = f.IssueFactory.create(project=project, owner=owner1)
|
||||||
|
issue3 = f.IssueFactory.create(project=project, owner=owner1)
|
||||||
|
|
||||||
|
issues = Issue.objects.filter(project=project).order_by("-owner__first_name")
|
||||||
|
|
||||||
|
issue1_neighbors = n.get_neighbors(issue1, results_set=issues)
|
||||||
|
issue3_neighbors = n.get_neighbors(issue3, results_set=issues)
|
||||||
|
|
||||||
|
assert issue1_neighbors.left is None
|
||||||
|
assert issue1_neighbors.right == issue2
|
||||||
|
assert issue3_neighbors.left == issue2
|
||||||
|
assert issue3_neighbors.right is None
|
||||||
|
|
||||||
|
def test_ordering_by_assigned_to(self):
|
||||||
|
project = f.ProjectFactory.create()
|
||||||
|
assigned_to1 = f.UserFactory.create(first_name="Chuck", last_name="Norris")
|
||||||
|
assigned_to2 = f.UserFactory.create(first_name="George", last_name="Of The Jungle")
|
||||||
|
|
||||||
|
issue1 = f.IssueFactory.create(project=project, assigned_to=assigned_to2)
|
||||||
|
issue2 = f.IssueFactory.create(project=project, assigned_to=assigned_to1)
|
||||||
|
issue3 = f.IssueFactory.create(project=project, assigned_to=assigned_to1)
|
||||||
|
|
||||||
|
issues = Issue.objects.filter(project=project).order_by("assigned_to__first_name")
|
||||||
|
|
||||||
|
issue2_neighbors = n.get_neighbors(issue2, results_set=issues)
|
||||||
|
issue3_neighbors = n.get_neighbors(issue3, results_set=issues)
|
||||||
|
|
||||||
|
assert issue2_neighbors.left is None
|
||||||
|
assert issue2_neighbors.right == issue3
|
||||||
|
assert issue3_neighbors.left == issue2
|
||||||
|
assert issue3_neighbors.right == issue1
|
||||||
|
|
||||||
|
def test_ordering_by_assigned_to_desc(self):
|
||||||
|
project = f.ProjectFactory.create()
|
||||||
|
assigned_to1 = f.UserFactory.create(first_name="Chuck", last_name="Norris")
|
||||||
|
assigned_to2 = f.UserFactory.create(first_name="George", last_name="Of The Jungle")
|
||||||
|
|
||||||
|
issue1 = f.IssueFactory.create(project=project, assigned_to=assigned_to2)
|
||||||
|
issue2 = f.IssueFactory.create(project=project, assigned_to=assigned_to1)
|
||||||
|
issue3 = f.IssueFactory.create(project=project, assigned_to=assigned_to1)
|
||||||
|
|
||||||
|
issues = Issue.objects.filter(project=project).order_by("-assigned_to__first_name")
|
||||||
|
|
||||||
|
issue1_neighbors = n.get_neighbors(issue1, results_set=issues)
|
||||||
|
issue3_neighbors = n.get_neighbors(issue3, results_set=issues)
|
||||||
|
|
||||||
|
assert issue1_neighbors.left is None
|
||||||
|
assert issue1_neighbors.right == issue2
|
||||||
|
assert issue3_neighbors.left == issue2
|
||||||
|
assert issue3_neighbors.right is None
|
Loading…
Reference in New Issue