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):
_______________________________________________ Mailing list: https://launchpad.net/~launchpad-reviewers Post to : launchpad-reviewers@lists.launchpad.net Unsubscribe : https://launchpad.net/~launchpad-reviewers More help : https://help.launchpad.net/ListHelp