Moving neighbors logic into standalone module

remotes/origin/enhancement/email-actions
Anler Hp 2014-05-20 16:14:02 +02:00 committed by David Barragán Merino
parent ac49a76146
commit 8003abbbef
10 changed files with 504 additions and 272 deletions

View File

@ -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):

View File

@ -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

122
taiga/base/neighbors.py Normal file
View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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