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?)
|
||||
|
||||
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):
|
||||
def pre_conditions_on_save(self, obj):
|
||||
|
|
|
@ -23,87 +23,3 @@ monkey.patch_api_view()
|
|||
monkey.patch_serializer()
|
||||
monkey.patch_import_module()
|
||||
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.models import Domain
|
||||
from .neighbors import get_neighbors
|
||||
|
||||
|
||||
class PickleField(serializers.WritableField):
|
||||
|
@ -72,9 +73,12 @@ class NeighborsSerializerMixin:
|
|||
def get_neighbors(self, obj):
|
||||
view, request = self.context.get("view", None), self.context.get("request", None)
|
||||
if view and request:
|
||||
queryset = view.filter_queryset(view.get_queryset(), True)
|
||||
previous, next = obj.get_neighbors(queryset)
|
||||
queryset = view.filter_queryset(view.get_queryset())
|
||||
left, right = get_neighbors(obj, results_set=queryset)
|
||||
else:
|
||||
left = right = None
|
||||
|
||||
return {"previous": self.serialize_neighbor(previous),
|
||||
"next": self.serialize_neighbor(next)}
|
||||
return {"previous": None, "next": None}
|
||||
return {
|
||||
"previous": self.serialize_neighbor(left),
|
||||
"next": self.serialize_neighbor(right)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ from taiga.base import filters
|
|||
from taiga.base import exceptions as exc
|
||||
from taiga.base.decorators import list_route
|
||||
from taiga.base.api import ModelCrudViewSet
|
||||
from taiga.base.api import NeighborsApiMixin
|
||||
|
||||
from taiga.projects.mixins.notifications import NotificationSenderMixin
|
||||
|
||||
from . import models
|
||||
|
@ -87,6 +87,7 @@ class IssuesFilter(filters.FilterBackend):
|
|||
class IssuesOrdering(filters.FilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
order_by = request.QUERY_PARAMS.get('order_by', None)
|
||||
|
||||
if order_by in ['owner', '-owner', 'assigned_to', '-assigned_to']:
|
||||
return queryset.order_by(
|
||||
'{}__first_name'.format(order_by),
|
||||
|
@ -95,7 +96,7 @@ class IssuesOrdering(filters.FilterBackend):
|
|||
return queryset
|
||||
|
||||
|
||||
class IssueViewSet(NeighborsApiMixin, NotificationSenderMixin, ModelCrudViewSet):
|
||||
class IssueViewSet(NotificationSenderMixin, ModelCrudViewSet):
|
||||
model = models.Issue
|
||||
queryset = models.Issue.objects.all().prefetch_related("attachments")
|
||||
serializer_class = serializers.IssueNeighborsSerializer
|
||||
|
|
|
@ -23,13 +23,12 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from picklefield.fields import PickledObjectField
|
||||
|
||||
from taiga.base.models import NeighborsMixin
|
||||
from taiga.base.utils.slug import ref_uniquely
|
||||
from taiga.projects.notifications.models import WatchedMixin
|
||||
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,
|
||||
verbose_name=_("ref"))
|
||||
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):
|
||||
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
|
||||
def is_closed(self):
|
||||
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.permissions import has_project_perm
|
||||
from taiga.base.api import ModelCrudViewSet
|
||||
from taiga.base.api import NeighborsApiMixin
|
||||
|
||||
from taiga.projects.mixins.notifications import NotificationSenderMixin
|
||||
from taiga.projects.models import Project
|
||||
from taiga.projects.history.services import take_snapshot
|
||||
|
@ -39,7 +39,7 @@ from . import serializers
|
|||
from . import services
|
||||
|
||||
|
||||
class UserStoryViewSet(NeighborsApiMixin, NotificationSenderMixin, ModelCrudViewSet):
|
||||
class UserStoryViewSet(NotificationSenderMixin, ModelCrudViewSet):
|
||||
model = models.UserStory
|
||||
serializer_class = serializers.UserStoryNeighborsSerializer
|
||||
list_serializer_class = serializers.UserStorySerializer
|
||||
|
|
|
@ -22,7 +22,6 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from picklefield.fields import PickledObjectField
|
||||
|
||||
from taiga.base.models import NeighborsMixin
|
||||
from taiga.base.utils.slug import ref_uniquely
|
||||
from taiga.projects.notifications.models import WatchedMixin
|
||||
from taiga.projects.mixins.blocked import BlockedMixin
|
||||
|
@ -52,7 +51,8 @@ class RolePoints(models.Model):
|
|||
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,
|
||||
verbose_name=_("ref"))
|
||||
milestone = models.ForeignKey("milestones.Milestone", null=True, blank=True,
|
||||
|
@ -112,16 +112,6 @@ class UserStory(NeighborsMixin, WatchedMixin, BlockedMixin, models.Model):
|
|||
def __repr__(self):
|
||||
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):
|
||||
return self.role_points
|
||||
|
||||
|
|
|
@ -5,6 +5,9 @@ from django.conf import settings
|
|||
|
||||
import taiga.domains.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.userstorage.models
|
||||
|
||||
|
@ -76,3 +79,61 @@ class StorageEntryFactory(factory.DjangoModelFactory):
|
|||
owner = factory.SubFactory("tests.factories.UserFactory")
|
||||
key = factory.Sequence(lambda n: "key-{}".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