launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #32942
[Merge] ~finnrg/launchpad:feat/LP-2652-searchQuestion-datecreated into launchpad:master
Finn Gärtner has proposed merging ~finnrg/launchpad:feat/LP-2652-searchQuestion-datecreated into launchpad:master.
Commit message:
feat: Extend searchQuestions API with new parameters created_before and created_since
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #2112473 in Launchpad itself: "Add date filtering to `searchQuestions` API call"
https://bugs.launchpad.net/launchpad/+bug/2112473
For more details, see:
https://code.launchpad.net/~finnrg/launchpad/+git/launchpad/+merge/492040
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~finnrg/launchpad:feat/LP-2652-searchQuestion-datecreated into launchpad:master.
diff --git a/lib/lp/answers/interfaces/questioncollection.py b/lib/lp/answers/interfaces/questioncollection.py
index e6d370b..83e778e 100644
--- a/lib/lp/answers/interfaces/questioncollection.py
+++ b/lib/lp/answers/interfaces/questioncollection.py
@@ -21,7 +21,7 @@ from lazr.restful.declarations import (
)
from lazr.restful.fields import ReferenceChoice
from zope.interface import Attribute, Interface
-from zope.schema import Choice, Int, List, TextLine
+from zope.schema import Choice, Datetime, Int, List, TextLine
from lp import _
from lp.answers.enums import (
@@ -48,6 +48,20 @@ class IQuestionCollection(Interface):
value_type=ReferenceChoice(vocabulary="Language"),
),
sort=Choice(title=_("Sort"), required=False, vocabulary=QuestionSort),
+ created_before=Datetime(
+ title=_(
+ "Search for questions that were created"
+ "before the given date."
+ ),
+ required=False,
+ ),
+ created_since=Datetime(
+ title=_(
+ "Search for questions that were created"
+ "since the given date."
+ ),
+ required=False,
+ ),
)
@operation_returns_collection_of(Interface) # IQuestion.
@export_read_operation()
@@ -57,6 +71,8 @@ class IQuestionCollection(Interface):
status=list(QUESTION_STATUS_DEFAULT_SEARCH),
language=None,
sort=None,
+ created_before=None,
+ created_since=None,
):
"""Return the questions from the collection matching search criteria.
@@ -74,6 +90,12 @@ class IQuestionCollection(Interface):
:param sort: An attribute of QuestionSort. If None, a default value is
used. When there is a search_text value, the default is to sort by
RELEVANCY, otherwise results are sorted NEWEST_FIRST.
+
+ :param created_since: Only return results whose `datecreated` property
+ is greater than or equal to this date.
+
+ :param created_before: Only return results whose `datecreated` property
+ is smaller than this date.
"""
def getQuestionLanguages():
@@ -118,6 +140,8 @@ class ISearchableByQuestionOwner(IQuestionCollection):
sort=None,
owner=None,
needs_attention_from=None,
+ created_before=None,
+ created_since=None,
):
"""Return the questions from the collection matching search criteria.
@@ -139,6 +163,10 @@ class ISearchableByQuestionOwner(IQuestionCollection):
:param sort: An attribute of QuestionSort. If None, a default value is
used. When there is a search_text value, the default is to sort by
RELEVANCY, otherwise results are sorted NEWEST_FIRST.
+ :param created_since: Only return results whose `datecreated` property
+ is greater than or equal to this date.
+ :param created_before: Only return results whose `datecreated` property
+ is smaller than this date.
"""
diff --git a/lib/lp/answers/interfaces/questionsperson.py b/lib/lp/answers/interfaces/questionsperson.py
index 2561a74..c4ece40 100644
--- a/lib/lp/answers/interfaces/questionsperson.py
+++ b/lib/lp/answers/interfaces/questionsperson.py
@@ -13,7 +13,7 @@ from lazr.restful.declarations import (
)
from lazr.restful.fields import ReferenceChoice
from zope.interface import Interface
-from zope.schema import Bool, Choice, List, TextLine
+from zope.schema import Bool, Choice, Datetime, List, TextLine
from lp import _
from lp.answers.enums import (
@@ -67,6 +67,20 @@ class IQuestionsPerson(IQuestionCollection):
title=_("Needs attentions from"), default=False, required=False
),
sort=Choice(title=_("Sort"), required=False, vocabulary=QuestionSort),
+ created_before=Datetime(
+ title=_(
+ "Search for questions that were created"
+ "before the given date."
+ ),
+ required=False,
+ ),
+ created_since=Datetime(
+ title=_(
+ "Search for questions that were created"
+ "since the given date."
+ ),
+ required=False,
+ ),
)
@operation_returns_collection_of(Interface) # IQuestion.
@export_read_operation()
@@ -80,6 +94,8 @@ class IQuestionsPerson(IQuestionCollection):
sort=None,
participation=None,
needs_attention=None,
+ created_before=None,
+ created_since=None,
):
"""Search the person's questions.
@@ -104,4 +120,8 @@ class IQuestionsPerson(IQuestionCollection):
:param sort: An attribute of QuestionSort. If None, a default value is
used. When there is a search_text value, the default is to sort by
RELEVANCY, otherwise results are sorted NEWEST_FIRST.
+ :param created_since: Only return results whose `datecreated` property
+ is greater than or equal to this date.
+ :param created_before: Only return results whose `datecreated` property
+ is smaller than this date.
"""
diff --git a/lib/lp/answers/model/question.py b/lib/lp/answers/model/question.py
index bd760b9..853fe2d 100644
--- a/lib/lp/answers/model/question.py
+++ b/lib/lp/answers/model/question.py
@@ -887,6 +887,8 @@ class QuestionSet:
language=None,
status=QUESTION_STATUS_DEFAULT_SEARCH,
sort=None,
+ created_before=None,
+ created_since=None,
):
"""See `IQuestionSet`"""
return QuestionSearch(
@@ -894,6 +896,8 @@ class QuestionSet:
status=status,
language=language,
sort=sort,
+ created_before=created_before,
+ created_since=created_since,
).getResults()
def getQuestionLanguages(self):
@@ -1073,6 +1077,8 @@ class QuestionSearch:
distribution=None,
sourcepackagename=None,
projectgroup=None,
+ created_before=None,
+ created_since=None,
):
self.search_text = search_text
self.nl_phrase_used = False
@@ -1098,6 +1104,8 @@ class QuestionSearch:
self.distribution = distribution
self.sourcepackagename = sourcepackagename
self.projectgroup = projectgroup
+ self.created_before = created_before
+ self.created_since = created_since
def getTargetConstraints(self):
"""Return the constraints related to the IQuestionTarget context."""
@@ -1221,6 +1229,12 @@ class QuestionSearch:
)
)
+ if self.created_before:
+ constraints.append(Question.datecreated < self.created_before)
+
+ if self.created_since:
+ constraints.append(Question.datecreated >= self.created_since)
+
return constraints
def getPrejoins(self):
@@ -1348,6 +1362,8 @@ class QuestionTargetSearch(QuestionSearch):
product=None,
distribution=None,
sourcepackagename=None,
+ created_before=None,
+ created_since=None,
):
assert (
product is not None
@@ -1365,6 +1381,8 @@ class QuestionTargetSearch(QuestionSearch):
product=product,
distribution=distribution,
sourcepackagename=sourcepackagename,
+ created_before=created_before,
+ created_since=created_since,
)
if owner:
@@ -1450,6 +1468,8 @@ class QuestionPersonSearch(QuestionSearch):
sort=None,
participation=None,
needs_attention=False,
+ created_before=None,
+ created_since=None,
):
if needs_attention:
needs_attention_from = person
@@ -1463,6 +1483,8 @@ class QuestionPersonSearch(QuestionSearch):
language=language,
needs_attention_from=needs_attention_from,
sort=sort,
+ created_before=created_before,
+ created_since=created_since,
)
assert IPerson.providedBy(person), "expected IPerson, got %r" % person
diff --git a/lib/lp/answers/model/questionsperson.py b/lib/lp/answers/model/questionsperson.py
index 964c050..3c907cb 100644
--- a/lib/lp/answers/model/questionsperson.py
+++ b/lib/lp/answers/model/questionsperson.py
@@ -28,6 +28,8 @@ class QuestionsPersonMixin:
sort=None,
participation=None,
needs_attention=None,
+ created_before=None,
+ created_since=None,
):
"""See `IQuestionsPerson`."""
return QuestionPersonSearch(
@@ -38,6 +40,8 @@ class QuestionsPersonMixin:
sort=sort,
participation=participation,
needs_attention=needs_attention,
+ created_before=created_before,
+ created_since=created_since,
).getResults()
def getQuestionLanguages(self):
diff --git a/lib/lp/answers/tests/test_question.py b/lib/lp/answers/tests/test_question.py
index 29adfe5..f9599b3 100644
--- a/lib/lp/answers/tests/test_question.py
+++ b/lib/lp/answers/tests/test_question.py
@@ -1,6 +1,8 @@
# Copyright 2013-2017 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
+from datetime import datetime, timedelta, timezone
+
from testtools.testcase import ExpectedException
from zope.component import getUtility
from zope.security.interfaces import Unauthorized
@@ -72,3 +74,47 @@ class TestQuestionSearch(TestCaseWithFactory):
questions = list(getUtility(IQuestionSet).searchQuestions())
self.assertIn(active_question, questions)
self.assertNotIn(inactive_question, questions)
+
+ def test_created_before(self):
+ today = datetime.now(timezone.utc)
+ nine_days_ago = today - timedelta(days=9)
+
+ q_nine_days_ago = self.factory.makeQuestion(
+ datecreated=today - timedelta(days=9)
+ )
+ q_ten_days_ago = self.factory.makeQuestion(
+ datecreated=today - timedelta(days=10)
+ )
+
+ questions = list(
+ getUtility(IQuestionSet).searchQuestions(
+ created_before=nine_days_ago
+ )
+ )
+
+ # Requires using assertIn/assertNotIn instead of assertEqual
+ # because database already contains multiple questions
+ self.assertIn(q_ten_days_ago, questions)
+ self.assertNotIn(q_nine_days_ago, questions)
+
+ def test_created_since(self):
+ today = datetime.now(timezone.utc)
+ nine_days_ago = today - timedelta(days=9)
+
+ q_nine_days_ago = self.factory.makeQuestion(
+ datecreated=today - timedelta(days=9)
+ )
+ q_ten_days_ago = self.factory.makeQuestion(
+ datecreated=today - timedelta(days=10)
+ )
+
+ questions = list(
+ getUtility(IQuestionSet).searchQuestions(
+ created_since=nine_days_ago
+ )
+ )
+
+ # Requires using assertIn/assertNotIn instead of assertEqual
+ # because database already contains multiple questions
+ self.assertIn(q_nine_days_ago, questions)
+ self.assertNotIn(q_ten_days_ago, questions)
diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
index 646475d..d34e4e0 100644
--- a/lib/lp/registry/model/distribution.py
+++ b/lib/lp/registry/model/distribution.py
@@ -1443,6 +1443,8 @@ class Distribution(
owner=None,
needs_attention_from=None,
unsupported=False,
+ created_before=None,
+ created_since=None,
):
"""See `IQuestionCollection`."""
if unsupported:
@@ -1459,6 +1461,8 @@ class Distribution(
owner=owner,
needs_attention_from=needs_attention_from,
unsupported_target=unsupported_target,
+ created_before=created_before,
+ created_since=created_since,
).getResults()
def getTargetTypes(self):
diff --git a/lib/lp/registry/model/projectgroup.py b/lib/lp/registry/model/projectgroup.py
index 1ad7db5..31df8f9 100644
--- a/lib/lp/registry/model/projectgroup.py
+++ b/lib/lp/registry/model/projectgroup.py
@@ -376,6 +376,8 @@ class ProjectGroup(
owner=None,
needs_attention_from=None,
unsupported=False,
+ created_before=None,
+ created_since=None,
):
"""See `IQuestionCollection`."""
if unsupported:
@@ -392,6 +394,8 @@ class ProjectGroup(
owner=owner,
needs_attention_from=needs_attention_from,
unsupported_target=unsupported_target,
+ created_before=created_before,
+ created_since=created_since,
).getResults()
def getQuestionLanguages(self):
diff --git a/lib/lp/registry/model/sourcepackage.py b/lib/lp/registry/model/sourcepackage.py
index 9197ca9..e3a5954 100644
--- a/lib/lp/registry/model/sourcepackage.py
+++ b/lib/lp/registry/model/sourcepackage.py
@@ -106,6 +106,8 @@ class SourcePackageQuestionTargetMixin(QuestionTargetMixin):
owner=None,
needs_attention_from=None,
unsupported=False,
+ created_before=None,
+ created_since=None,
):
"""See `IQuestionCollection`."""
if unsupported:
@@ -123,6 +125,8 @@ class SourcePackageQuestionTargetMixin(QuestionTargetMixin):
owner=owner,
needs_attention_from=needs_attention_from,
unsupported_target=unsupported_target,
+ created_before=created_before,
+ created_since=created_since,
).getResults()
def getAnswerContactsForLanguage(self, language):
Follow ups