diff --git a/project/api/event/resources.py b/project/api/event/resources.py index 2943ae4..eb06436 100644 --- a/project/api/event/resources.py +++ b/project/api/event/resources.py @@ -35,7 +35,7 @@ from project.services.event import ( get_significant_event_changes, update_event, ) -from project.services.event_search import EventSearchParams +from project.services.search_params import EventSearchParams from project.views.event import ( send_event_report_mails, send_referenced_event_changed_mails, @@ -62,17 +62,20 @@ class EventListResource(BaseResource): @marshal_with(EventListResponseSchema) @require_api_access() def get(self, **kwargs): - pagination = ( - Event.query.join(Event.admin_unit) - .filter( - and_( - Event.public_status == PublicStatus.published, - AdminUnit.is_verified, - ) + params = EventSearchParams() + params.load_from_request(**kwargs) + + query = Event.query.join(Event.admin_unit).filter( + and_( + Event.public_status == PublicStatus.published, + AdminUnit.is_verified, ) - .paginate() ) - return pagination + query = params.get_trackable_query(query, Event) + query = params.get_trackable_order_by(query, Event) + query = query.order_by(Event.min_start) + + return query.paginate() class EventResource(BaseResource): @@ -173,7 +176,7 @@ class EventSearchResource(BaseResource): def get(self, **kwargs): login_api_user() params = EventSearchParams() - params.load_from_request() + params.load_from_request(**kwargs) pagination = get_events_query(params).paginate() return pagination diff --git a/project/api/event/schemas.py b/project/api/event/schemas.py index 7f8fe87..fb9325d 100644 --- a/project/api/event/schemas.py +++ b/project/api/event/schemas.py @@ -37,6 +37,7 @@ from project.api.schemas import ( PaginationRequestSchema, PaginationResponseSchema, SQLAlchemyBaseSchema, + TrackableRequestSchemaMixin, TrackableSchemaMixin, WriteIdSchemaMixin, ) @@ -222,7 +223,10 @@ class EventRefSchema(EventIdSchema): class EventSearchItemSchema( - EventRefSchema, EventCurrentUserMixin, EventCurrentOrganizationMixin + EventRefSchema, + EventCurrentUserMixin, + EventCurrentOrganizationMixin, + TrackableSchemaMixin, ): description = marshmallow.auto_field() date_definitions = fields.List(fields.Nested(EventDateDefinitionSchema)) @@ -237,27 +241,53 @@ class EventSearchItemSchema( public_status = EnumField(PublicStatus) -class EventListRequestSchema(PaginationRequestSchema): +class EventListRequestSchema(PaginationRequestSchema, TrackableRequestSchemaMixin): + sort = fields.Str( + metadata={"description": "Sort result items."}, + validate=validate.OneOf( + [ + "-created_at", + "-updated_at", + "-last_modified_at", + "start", + ] + ), + ) + + +class EventListRefSchema(EventRefSchema, TrackableSchemaMixin): pass class EventListResponseSchema(PaginationResponseSchema): items = fields.List( - fields.Nested(EventRefSchema), metadata={"description": "Events"} + fields.Nested(EventListRefSchema), metadata={"description": "Events"} ) -class UserFavoriteEventListRequestSchema(PaginationRequestSchema): - pass +class UserFavoriteEventListRequestSchema( + PaginationRequestSchema, TrackableRequestSchemaMixin +): + sort = fields.Str( + metadata={"description": "Sort result items."}, + validate=validate.OneOf( + [ + "-created_at", + "-updated_at", + "-last_modified_at", + "start", + ] + ), + ) class UserFavoriteEventListResponseSchema(PaginationResponseSchema): items = fields.List( - fields.Nested(EventRefSchema), metadata={"description": "Events"} + fields.Nested(EventListRefSchema), metadata={"description": "Events"} ) -class EventSearchRequestSchema(PaginationRequestSchema): +class EventSearchRequestSchema(PaginationRequestSchema, TrackableRequestSchemaMixin): keyword = fields.Str( metadata={"description": "Looks for keyword in name, description and tags."}, ) @@ -267,9 +297,7 @@ class EventSearchRequestSchema(PaginationRequestSchema): }, ) date_to = fields.Date( - metadata={ - "description": "Looks for events at or before this date, e.g. 2020-12-31." - }, + metadata={"description": "Looks for events before this date, e.g. 2020-12-31."}, ) coordinate = fields.Str( metadata={ @@ -294,11 +322,38 @@ class EventSearchRequestSchema(PaginationRequestSchema): ) sort = fields.Str( metadata={"description": "Sort result items."}, + validate=validate.OneOf( + [ + "-created_at", + "-updated_at", + "-last_modified_at", + "-rating", + "-reference_created_at", + "start", + ] + ), ) status = fields.List( EnumField(EventStatus), metadata={"description": "Looks for events with this stati."}, ) + postal_code = fields.List( + fields.Str(), + metadata={"description": "Looks for events with places with this postal code."}, + ) + exclude_recurring = fields.Bool( + metadata={"description": "Exclude recurring events"}, + ) + include_organization_references = fields.Bool( + metadata={ + "description": "Include events referenced by organization. Only valid if there is an organization context." + }, + ) + organization_references_only = fields.Bool( + metadata={ + "description": "Only events referenced by organization. Only valid if there is an organization context." + }, + ) class EventSearchResponseSchema(PaginationResponseSchema): diff --git a/project/api/event_date/resources.py b/project/api/event_date/resources.py index 2eca7c6..e5d3215 100644 --- a/project/api/event_date/resources.py +++ b/project/api/event_date/resources.py @@ -16,7 +16,7 @@ from project.api.event_date.schemas import ( from project.api.resources import BaseResource, require_api_access from project.models import AdminUnit, Event, EventDate, PublicStatus from project.services.event import get_event_dates_query -from project.services.event_search import EventSearchParams +from project.services.search_params import EventSearchParams from project.views.utils import get_current_admin_unit_for_api @@ -26,7 +26,7 @@ class EventDateListResource(BaseResource): @marshal_with(EventDateListResponseSchema) @require_api_access() def get(self, **kwargs): - pagination = ( + query = ( EventDate.query.join(EventDate.event) .join(Event.admin_unit) .options(lazyload(EventDate.event)) @@ -36,9 +36,14 @@ class EventDateListResource(BaseResource): AdminUnit.is_verified, ) ) - .paginate() ) - return pagination + + params = EventSearchParams() + params.load_from_request(**kwargs) + query = params.get_trackable_query(query, Event) + query = params.get_trackable_order_by(query, Event) + query = query.order_by(EventDate.start) + return query.paginate() class EventDateResource(BaseResource): @@ -63,9 +68,9 @@ class EventDateSearchResource(BaseResource): def get(self, **kwargs): login_api_user() params = EventSearchParams() - params.load_from_request() - params.can_read_planned_events = can_use_planning() params.include_admin_unit_references = True + params.load_from_request(**kwargs) + params.can_read_planned_events = can_use_planning() if "not_referenced" in request.args: admin_unit = get_current_admin_unit_for_api() diff --git a/project/api/event_date/schemas.py b/project/api/event_date/schemas.py index 0ef75e4..8a03f65 100644 --- a/project/api/event_date/schemas.py +++ b/project/api/event_date/schemas.py @@ -1,4 +1,4 @@ -from marshmallow import fields +from marshmallow import fields, validate from project.api import marshmallow from project.api.event.schemas import ( @@ -7,7 +7,12 @@ from project.api.event.schemas import ( EventSearchRequestSchema, ) from project.api.fields import CustomDateTimeField -from project.api.schemas import PaginationRequestSchema, PaginationResponseSchema +from project.api.schemas import ( + PaginationRequestSchema, + PaginationResponseSchema, + TrackableRequestSchemaMixin, + TrackableSchemaMixin, +) from project.models import EventDate @@ -32,13 +37,27 @@ class EventDateRefSchema(marshmallow.SQLAlchemySchema): start = CustomDateTimeField() -class EventDateListRequestSchema(PaginationRequestSchema): +class EventDateListRequestSchema(PaginationRequestSchema, TrackableRequestSchemaMixin): + sort = fields.Str( + metadata={"description": "Sort result items."}, + validate=validate.OneOf( + [ + "-created_at", + "-updated_at", + "-last_modified_at", + "start", + ] + ), + ) + + +class EventDateListRefSchema(EventDateRefSchema, TrackableSchemaMixin): pass class EventDateListResponseSchema(PaginationResponseSchema): items = fields.List( - fields.Nested(EventDateRefSchema), metadata={"description": "Dates"} + fields.Nested(EventDateListRefSchema), metadata={"description": "Dates"} ) diff --git a/project/api/event_list/resources.py b/project/api/event_list/resources.py index 76d9b45..d61faab 100644 --- a/project/api/event_list/resources.py +++ b/project/api/event_list/resources.py @@ -13,7 +13,7 @@ from project.api.event_list.schemas import ( from project.api.resources import BaseResource, require_api_access from project.models import Event, EventList from project.services.event import get_events_query -from project.services.event_search import EventSearchParams +from project.services.search_params import EventSearchParams class EventListModelResource(BaseResource): diff --git a/project/api/event_reference/schemas.py b/project/api/event_reference/schemas.py index f9561f2..30eadc6 100644 --- a/project/api/event_reference/schemas.py +++ b/project/api/event_reference/schemas.py @@ -1,4 +1,4 @@ -from marshmallow import fields +from marshmallow import fields, validate from project.api import marshmallow from project.api.event.schemas import EventRefSchema, EventWriteIdSchema @@ -8,6 +8,8 @@ from project.api.schemas import ( PaginationRequestSchema, PaginationResponseSchema, SQLAlchemyBaseSchema, + TrackableRequestSchemaMixin, + TrackableSchemaMixin, ) from project.models import EventReference @@ -22,7 +24,7 @@ class EventReferenceIdSchema(EventReferenceModelSchema, IdSchemaMixin): pass -class EventReferenceRefSchema(EventReferenceIdSchema): +class EventReferenceRefSchema(EventReferenceIdSchema, TrackableSchemaMixin): event = fields.Nested(EventRefSchema) @@ -36,8 +38,13 @@ class EventReferenceDumpSchema(EventReferenceIdSchema): organization_id = fields.Int(attribute="admin_unit_id") -class EventReferenceListRequestSchema(PaginationRequestSchema): - pass +class EventReferenceListRequestSchema( + PaginationRequestSchema, TrackableRequestSchemaMixin +): + sort = fields.Str( + metadata={"description": "Sort result items."}, + validate=validate.OneOf(["-created_at", "-updated_at", "-last_modified_at"]), + ) class EventReferenceListResponseSchema(PaginationResponseSchema): diff --git a/project/api/fields.py b/project/api/fields.py index e181a4e..20b6620 100644 --- a/project/api/fields.py +++ b/project/api/fields.py @@ -1,7 +1,7 @@ from marshmallow import ValidationError, fields from marshmallow_sqlalchemy import fields as msfields -from project.dateutils import berlin_tz +from project.dateutils import berlin_tz, gmt_tz class NumericStr(fields.String): @@ -34,6 +34,22 @@ class CustomDateTimeField(fields.DateTime): return result +class GmtDateTimeField(fields.DateTime): + def _serialize(self, value, attr, obj, **kwargs): + if value: + value = value.replace(tzinfo=gmt_tz) + + return super()._serialize(value, attr, obj, **kwargs) + + def deserialize(self, value, attr, data, **kwargs): + result = super().deserialize(value, attr, data, **kwargs) + + if result and result.tzinfo is None: + result = gmt_tz.localize(result) + + return result + + class Owned(msfields.Nested): def _deserialize(self, *args, **kwargs): if ( diff --git a/project/api/organization/resources.py b/project/api/organization/resources.py index 1adfa44..f8d6897 100644 --- a/project/api/organization/resources.py +++ b/project/api/organization/resources.py @@ -77,7 +77,7 @@ from project.api.place.schemas import ( ) from project.api.resources import BaseResource, require_api_access from project.models import AdminUnit, Event, PublicStatus -from project.models.admin_unit import AdminUnitRelation +from project.models.admin_unit import AdminUnitInvitation, AdminUnitRelation from project.services.admin_unit import ( get_admin_unit_invitation_query, get_admin_unit_query, @@ -88,13 +88,20 @@ from project.services.admin_unit import ( get_place_query, ) from project.services.event import get_event_dates_query, get_events_query, insert_event -from project.services.event_search import EventSearchParams from project.services.importer.event_importer import EventImporter from project.services.reference import ( get_reference_incoming_query, get_reference_outgoing_query, get_relation_outgoing_query, ) +from project.services.search_params import ( + AdminUnitSearchParams, + EventPlaceSearchParams, + EventReferenceSearchParams, + EventSearchParams, + OrganizerSearchParams, + TrackableSearchParams, +) from project.views.utils import get_current_admin_unit_for_api, send_mail_async @@ -119,10 +126,10 @@ class OrganizationEventDateSearchResource(BaseResource): admin_unit = AdminUnit.query.get_or_404(id) params = EventSearchParams() - params.load_from_request() + params.include_admin_unit_references = True + params.load_from_request(**kwargs) params.admin_unit_id = admin_unit.id params.can_read_private_events = api_can_read_private_events(admin_unit) - params.include_admin_unit_references = True pagination = get_event_dates_query(params).paginate() return pagination @@ -137,7 +144,7 @@ class OrganizationEventSearchResource(BaseResource): admin_unit = AdminUnit.query.get_or_404(id) params = EventSearchParams() - params.load_from_request() + params.load_from_request(**kwargs) params.admin_unit_id = admin_unit.id params.can_read_private_events = api_can_read_private_events(admin_unit) @@ -151,8 +158,10 @@ class OrganizationEventListResource(BaseResource): @marshal_with(EventListResponseSchema) @require_api_access() def get(self, id, **kwargs): - admin_unit = AdminUnit.query.get_or_404(id) + params = EventSearchParams() + params.load_from_request(**kwargs) + admin_unit = AdminUnit.query.get_or_404(id) event_filter = Event.admin_unit_id == admin_unit.id if not api_can_read_private_events(admin_unit): @@ -162,8 +171,12 @@ class OrganizationEventListResource(BaseResource): AdminUnit.is_verified, ) - pagination = Event.query.join(Event.admin_unit).filter(event_filter).paginate() - return pagination + query = Event.query.join(Event.admin_unit).filter(event_filter) + query = params.get_trackable_query(query, Event) + query = params.get_trackable_order_by(query, Event) + query = query.order_by(Event.min_start) + + return query.paginate() @doc( summary="Add new event", @@ -223,8 +236,6 @@ class OrganizationListResource(BaseResource): @marshal_with(OrganizationListResponseSchema) @require_api_access() def get(self, **kwargs): - keyword = kwargs["keyword"] if "keyword" in kwargs else None - login_api_user() include_unverified = can_verify_admin_unit() reference_request_for_admin_unit_id = None @@ -235,11 +246,11 @@ class OrganizationListResource(BaseResource): if admin_unit: reference_request_for_admin_unit_id = admin_unit.id - pagination = get_admin_unit_query( - keyword, - include_unverified, - reference_request_for_admin_unit_id=reference_request_for_admin_unit_id, - ).paginate() + params = AdminUnitSearchParams() + params.load_from_request(**kwargs) + params.include_unverified = include_unverified + params.reference_request_for_admin_unit_id = reference_request_for_admin_unit_id + pagination = get_admin_unit_query(params).paginate() return pagination @@ -252,9 +263,11 @@ class OrganizationOrganizerListResource(BaseResource): @require_api_access() def get(self, id, **kwargs): admin_unit = AdminUnit.query.get_or_404(id) - name = kwargs["name"] if "name" in kwargs else None - pagination = get_organizer_query(admin_unit.id, name).paginate() + params = OrganizerSearchParams() + params.load_from_request(**kwargs) + params.admin_unit_id = admin_unit.id + pagination = get_organizer_query(params).paginate() return pagination @doc( @@ -285,9 +298,11 @@ class OrganizationPlaceListResource(BaseResource): @require_api_access() def get(self, id, **kwargs): admin_unit = AdminUnit.query.get_or_404(id) - name = kwargs["name"] if "name" in kwargs else None - pagination = get_place_query(admin_unit.id, name).paginate() + params = EventPlaceSearchParams() + params.load_from_request(**kwargs) + params.admin_unit_id = admin_unit.id + pagination = get_place_query(params).paginate() return pagination @doc( @@ -322,7 +337,10 @@ class OrganizationIncomingEventReferenceListResource(BaseResource): def get(self, id, **kwargs): admin_unit = AdminUnit.query.get_or_404(id) - pagination = get_reference_incoming_query(admin_unit).paginate() + params = EventReferenceSearchParams() + params.load_from_request(**kwargs) + params.admin_unit_id = admin_unit.id + pagination = get_reference_incoming_query(params).paginate() return pagination @doc( @@ -430,8 +448,13 @@ class OrganizationOrganizationInvitationListResource(BaseResource): admin_unit = get_admin_unit_for_manage_or_404(id) access_or_401(admin_unit, "admin_unit:update") - pagination = get_admin_unit_invitation_query(admin_unit).paginate() - return pagination + params = TrackableSearchParams() + params.load_from_request(**kwargs) + + query = get_admin_unit_invitation_query(admin_unit) + query = params.get_trackable_query(query, AdminUnitInvitation) + query = params.get_trackable_order_by(query, AdminUnitInvitation) + return query.paginate() @doc( summary="Add new organization invitation", diff --git a/project/api/organization/schemas.py b/project/api/organization/schemas.py index 9bfe2f5..001cb27 100644 --- a/project/api/organization/schemas.py +++ b/project/api/organization/schemas.py @@ -1,4 +1,4 @@ -from marshmallow import fields, post_dump +from marshmallow import fields, post_dump, validate from project.access import has_access, login_api_user from project.api import marshmallow @@ -9,6 +9,8 @@ from project.api.schemas import ( PaginationRequestSchema, PaginationResponseSchema, SQLAlchemyBaseSchema, + TrackableRequestSchemaMixin, + TrackableSchemaMixin, WriteIdSchemaMixin, ) from project.models import AdminUnit @@ -66,15 +68,23 @@ class OrganizationRefSchema(OrganizationIdSchema): name = marshmallow.auto_field() -class OrganizationListRefSchema(OrganizationRefSchema): +class OrganizationListRefSchema(OrganizationRefSchema, TrackableSchemaMixin): short_name = marshmallow.auto_field() is_verified = fields.Boolean() -class OrganizationListRequestSchema(PaginationRequestSchema): +class OrganizationListRequestSchema( + PaginationRequestSchema, TrackableRequestSchemaMixin +): keyword = fields.Str( metadata={"description": "Looks for keyword in name and short name."}, ) + sort = fields.Str( + metadata={"description": "Sort result items."}, + validate=validate.OneOf( + ["-created_at", "-updated_at", "-last_modified_at", "name"] + ), + ) class OrganizationListResponseSchema(PaginationResponseSchema): diff --git a/project/api/organization_invitation/schemas.py b/project/api/organization_invitation/schemas.py index 06d41b7..4f4fc69 100644 --- a/project/api/organization_invitation/schemas.py +++ b/project/api/organization_invitation/schemas.py @@ -1,4 +1,4 @@ -from marshmallow import fields +from marshmallow import fields, validate from project.api import marshmallow from project.api.organization.schemas import OrganizationRefSchema @@ -7,6 +7,7 @@ from project.api.schemas import ( PaginationRequestSchema, PaginationResponseSchema, SQLAlchemyBaseSchema, + TrackableRequestSchemaMixin, TrackableSchemaMixin, ) from project.models import AdminUnitInvitation @@ -40,13 +41,26 @@ class OrganizationInvitationRefSchema(OrganizationInvitationIdSchema): email = marshmallow.auto_field() -class OrganizationInvitationListRequestSchema(PaginationRequestSchema): +class OrganizationInvitationListRequestSchema( + PaginationRequestSchema, TrackableRequestSchemaMixin +): + sort = fields.Str( + metadata={"description": "Sort result items."}, + validate=validate.OneOf( + ["-created_at", "-updated_at", "-last_modified_at", "name"] + ), + ) + + +class OrganizationInvitationListRefSchema( + OrganizationInvitationRefSchema, TrackableSchemaMixin +): pass class OrganizationInvitationListResponseSchema(PaginationResponseSchema): items = fields.List( - fields.Nested(OrganizationInvitationRefSchema), + fields.Nested(OrganizationInvitationListRefSchema), metadata={"description": "Organization invitations"}, ) diff --git a/project/api/organizer/schemas.py b/project/api/organizer/schemas.py index b164e93..6705e27 100644 --- a/project/api/organizer/schemas.py +++ b/project/api/organizer/schemas.py @@ -15,6 +15,7 @@ from project.api.schemas import ( PaginationRequestSchema, PaginationResponseSchema, SQLAlchemyBaseSchema, + TrackableRequestSchemaMixin, TrackableSchemaMixin, WriteIdSchemaMixin, ) @@ -67,15 +68,25 @@ class OrganizerRefSchema(OrganizerIdSchema): name = marshmallow.auto_field() -class OrganizerListRequestSchema(PaginationRequestSchema): +class OrganizerListRequestSchema(PaginationRequestSchema, TrackableRequestSchemaMixin): name = fields.Str( metadata={"description": "Looks for name."}, ) + sort = fields.Str( + metadata={"description": "Sort result items."}, + validate=validate.OneOf( + ["-created_at", "-updated_at", "-last_modified_at", "name"] + ), + ) + + +class OrganizerListRefSchema(OrganizerRefSchema, TrackableSchemaMixin): + pass class OrganizerListResponseSchema(PaginationResponseSchema): items = fields.List( - fields.Nested(OrganizerRefSchema), metadata={"description": "Organizers"} + fields.Nested(OrganizerListRefSchema), metadata={"description": "Organizers"} ) diff --git a/project/api/place/schemas.py b/project/api/place/schemas.py index 9f16c82..f671a61 100644 --- a/project/api/place/schemas.py +++ b/project/api/place/schemas.py @@ -16,6 +16,7 @@ from project.api.schemas import ( PaginationRequestSchema, PaginationResponseSchema, SQLAlchemyBaseSchema, + TrackableRequestSchemaMixin, TrackableSchemaMixin, WriteIdSchemaMixin, ) @@ -64,15 +65,25 @@ class PlaceSearchItemSchema(PlaceRefSchema): location = fields.Nested(LocationSearchItemSchema) -class PlaceListRequestSchema(PaginationRequestSchema): +class PlaceListRequestSchema(PaginationRequestSchema, TrackableRequestSchemaMixin): name = fields.Str( metadata={"description": "Looks for name."}, ) + sort = fields.Str( + metadata={"description": "Sort result items."}, + validate=validate.OneOf( + ["-created_at", "-updated_at", "-last_modified_at", "name"] + ), + ) + + +class PlaceListRefSchema(PlaceRefSchema, TrackableSchemaMixin): + pass class PlaceListResponseSchema(PaginationResponseSchema): items = fields.List( - fields.Nested(PlaceRefSchema), metadata={"description": "Places"} + fields.Nested(PlaceListRefSchema), metadata={"description": "Places"} ) diff --git a/project/api/schemas.py b/project/api/schemas.py index 8766984..56f7d14 100644 --- a/project/api/schemas.py +++ b/project/api/schemas.py @@ -2,6 +2,7 @@ from marshmallow import ValidationError, fields, missing, validate from marshmallow.decorators import pre_load from project.api import marshmallow +from project.api.fields import GmtDateTimeField class SQLAlchemyBaseSchema(marshmallow.SQLAlchemySchema): @@ -39,8 +40,21 @@ class WriteIdSchemaMixin(object): class TrackableSchemaMixin(object): - created_at = marshmallow.auto_field(dump_only=True) - updated_at = marshmallow.auto_field(dump_only=True) + created_at = GmtDateTimeField(dump_only=True) + updated_at = GmtDateTimeField(dump_only=True) + + +class TrackableRequestSchemaMixin(object): + created_at_from = GmtDateTimeField( + metadata={ + "description": "Items created at or after this date time in GTM, e.g. 2020-12-31T00:00:00." + }, + ) + created_at_to = GmtDateTimeField( + metadata={ + "description": "Items created before this date time in GTM, e.g. 2020-12-31T00:00:00." + }, + ) class ErrorResponseSchema(marshmallow.Schema): diff --git a/project/api/user/resources.py b/project/api/user/resources.py index f11b6f5..e18edf1 100644 --- a/project/api/user/resources.py +++ b/project/api/user/resources.py @@ -21,7 +21,7 @@ from project.api.resources import BaseResource, require_api_access from project.models import AdminUnitInvitation, Event from project.services.admin_unit import get_admin_unit_organization_invitations_query from project.services.event import get_events_query -from project.services.event_search import EventSearchParams +from project.services.search_params import EventSearchParams, TrackableSearchParams from project.utils import strings_are_equal_ignoring_case @@ -41,11 +41,14 @@ class UserOrganizationInvitationListResource(BaseResource): def get(self, **kwargs): login_api_user_or_401() - pagination = get_admin_unit_organization_invitations_query( - current_user.email - ).paginate() + params = TrackableSearchParams() + params.load_from_request(**kwargs) - return pagination + query = get_admin_unit_organization_invitations_query(current_user.email) + query = params.get_trackable_query(query, AdminUnitInvitation) + query = params.get_trackable_order_by(query, AdminUnitInvitation) + + return query.paginate() class UserOrganizationInvitationResource(BaseResource): @@ -92,8 +95,15 @@ class UserFavoriteEventListResource(BaseResource): login_api_user_or_401() - pagination = get_favorite_events_query(current_user.id).paginate() - return pagination + query = get_favorite_events_query(current_user.id) + + params = EventSearchParams() + params.load_from_request(**kwargs) + query = params.get_trackable_query(query, Event) + query = params.get_trackable_order_by(query, Event) + query = query.order_by(Event.min_start) + + return query.paginate() class UserFavoriteEventSearchResource(BaseResource): @@ -108,7 +118,7 @@ class UserFavoriteEventSearchResource(BaseResource): login_api_user_or_401() params = EventSearchParams() - params.load_from_request() + params.load_from_request(**kwargs) params.favored_by_user_id = current_user.id pagination = get_events_query(params).paginate() diff --git a/project/models/image.py b/project/models/image.py index a7d2ad8..474bdbe 100644 --- a/project/models/image.py +++ b/project/models/image.py @@ -2,7 +2,6 @@ from sqlalchemy import Column, Integer, String, Unicode from sqlalchemy.orm import deferred from project import db -from project.dateutils import gmt_tz from project.models.iowned import IOwned from project.models.trackable_mixin import TrackableMixin @@ -23,13 +22,6 @@ class Image(db.Model, TrackableMixin, IOwned): def is_empty(self): return not self.data - def get_hash(self): - return ( - int(self.updated_at.replace(tzinfo=gmt_tz).timestamp() * 1000) - if self.updated_at - else 0 - ) - def get_file_extension(self): return self.encoding_format.split("/")[-1] if self.encoding_format else "png" diff --git a/project/models/trackable_mixin.py b/project/models/trackable_mixin.py index 9d51a50..2cfe465 100644 --- a/project/models/trackable_mixin.py +++ b/project/models/trackable_mixin.py @@ -1,8 +1,10 @@ import datetime from sqlalchemy import Column, DateTime, ForeignKey +from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import backref, declared_attr, deferred, relationship +from project.dateutils import gmt_tz from project.models.functions import _current_user_id_or_none @@ -18,7 +20,6 @@ class TrackableMixin(object): return deferred( Column( DateTime, - default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow, ), group="trackable", @@ -50,7 +51,6 @@ class TrackableMixin(object): Column( "updated_by_id", ForeignKey("user.id", ondelete="SET NULL"), - default=_current_user_id_or_none, onupdate=_current_user_id_or_none, ), group="trackable", @@ -64,3 +64,14 @@ class TrackableMixin(object): remote_side="User.id", backref=backref("updated_%s" % cls.__tablename__, lazy=True), ) + + @hybrid_property + def last_modified_at(self): + return self.updated_at if self.updated_at else self.created_at + + def get_hash(self): + return ( + int(self.last_modified_at.replace(tzinfo=gmt_tz).timestamp() * 1000) + if self.last_modified_at + else 0 + ) diff --git a/project/services/admin_unit.py b/project/services/admin_unit.py index bff4e4e..1a8b2b0 100644 --- a/project/services/admin_unit.py +++ b/project/services/admin_unit.py @@ -24,6 +24,11 @@ from project.services.reference import ( get_newest_reference_requests, get_newest_references, ) +from project.services.search_params import ( + AdminUnitSearchParams, + EventPlaceSearchParams, + OrganizerSearchParams, +) def insert_admin_unit_for_user(admin_unit, user, invitation=None): @@ -175,59 +180,66 @@ def get_admin_unit_member(id): return AdminUnitMember.query.filter_by(id=id).first() -def get_admin_unit_query( - keyword=None, - include_unverified=False, - only_verifier=False, - reference_request_for_admin_unit_id=None, -): +def get_admin_unit_query(params: AdminUnitSearchParams): query = AdminUnit.query - if not include_unverified: + if not params.include_unverified: query = query.filter(AdminUnit.is_verified) - if only_verifier: + if params.only_verifier: only_verifier_filter = and_( AdminUnit.can_verify_other, AdminUnit.incoming_verification_requests_allowed ) query = query.filter(only_verifier_filter) - if reference_request_for_admin_unit_id: + if params.reference_request_for_admin_unit_id: request_filter = and_( - AdminUnit.id != reference_request_for_admin_unit_id, + AdminUnit.id != params.reference_request_for_admin_unit_id, AdminUnit.incoming_reference_requests_allowed, ) query = query.filter(request_filter) - if keyword: - like_keyword = "%" + keyword + "%" - order_keyword = keyword + "%" + query = params.get_trackable_query(query, AdminUnit) + + if params.keyword: + like_keyword = "%" + params.keyword + "%" + order_keyword = params.keyword + "%" keyword_filter = or_( AdminUnit.name.ilike(like_keyword), AdminUnit.short_name.ilike(like_keyword), ) - query = query.filter(keyword_filter).order_by( + query = query.filter(keyword_filter) + + query = params.get_trackable_order_by(query, AdminUnit) + query = query.order_by( AdminUnit.name.ilike(order_keyword).desc(), AdminUnit.short_name.ilike(order_keyword).desc(), func.lower(AdminUnit.name), ) else: + query = params.get_trackable_order_by(query, AdminUnit) query = query.order_by(func.lower(AdminUnit.name)) return query -def get_organizer_query(admin_unit_id, name=None): - query = EventOrganizer.query.filter(EventOrganizer.admin_unit_id == admin_unit_id) +def get_organizer_query(params: OrganizerSearchParams): + query = EventOrganizer.query.filter( + EventOrganizer.admin_unit_id == params.admin_unit_id + ) - if name: - like_name = "%" + name + "%" - order_name = name + "%" + query = params.get_trackable_query(query, EventOrganizer) + + if params.name: + like_name = "%" + params.name + "%" + order_name = params.name + "%" + query = params.get_trackable_order_by(query, EventOrganizer) query = query.filter(EventOrganizer.name.ilike(like_name)).order_by( EventOrganizer.name.ilike(order_name).desc(), func.lower(EventOrganizer.name), ) else: + query = params.get_trackable_order_by(query, EventOrganizer) query = query.order_by(func.lower(EventOrganizer.name)) return query @@ -243,13 +255,15 @@ def get_custom_widget_query(admin_unit_id, name=None): return query.order_by(func.lower(CustomWidget.name)) -def get_place_query(admin_unit_id, name=None): - query = EventPlace.query.filter(EventPlace.admin_unit_id == admin_unit_id) +def get_place_query(params: EventPlaceSearchParams): + query = EventPlace.query.filter(EventPlace.admin_unit_id == params.admin_unit_id) + query = params.get_trackable_query(query, EventPlace) - if name: - like_name = "%" + name + "%" + if params.name: + like_name = "%" + params.name + "%" query = query.filter(EventPlace.name.ilike(like_name)) + query = params.get_trackable_order_by(query, EventPlace) return query.order_by(func.lower(EventPlace.name)) @@ -378,7 +392,7 @@ def create_ical_events_for_admin_unit( from project.dateutils import get_today from project.services.event import create_ical_events_for_search - from project.services.event_search import EventSearchParams + from project.services.search_params import EventSearchParams params = EventSearchParams() params.date_from = get_today() - relativedelta(months=1) diff --git a/project/services/event.py b/project/services/event.py index 05421fc..8172ead 100644 --- a/project/services/event.py +++ b/project/services/event.py @@ -45,8 +45,8 @@ from project.models import ( UserFavoriteEvents, sanitize_allday_instance, ) -from project.services.event_search import EventSearchParams from project.services.reference import get_event_reference, upsert_event_reference +from project.services.search_params import EventSearchParams from project.utils import get_pending_changes, get_place_str from project.views.utils import truncate @@ -65,6 +65,8 @@ def upsert_event_category(category_name): def fill_event_filter(event_filter, params: EventSearchParams): + event_filter = params.fill_trackable_filter(event_filter, Event) + if params.keyword: tq = func.websearch_to_tsquery("german", params.keyword) event_filter = and_( @@ -196,19 +198,25 @@ def fill_event_admin_unit_filter(event_filter, params: EventSearchParams): admin_unit_reference = None if params.admin_unit_id: - if params.include_admin_unit_references: + if params.include_admin_unit_references or params.admin_unit_references_only: admin_unit_refs_subquery = EventReference.query.filter( EventReference.admin_unit_id == params.admin_unit_id ).subquery() admin_unit_reference = aliased(EventReference, admin_unit_refs_subquery) - event_filter = and_( - event_filter, - or_( - Event.admin_unit_id == params.admin_unit_id, + if params.admin_unit_references_only: + event_filter = and_( + event_filter, admin_unit_reference.id.isnot(None), - ), - ) + ) + else: + event_filter = and_( + event_filter, + or_( + Event.admin_unit_id == params.admin_unit_id, + admin_unit_reference.id.isnot(None), + ), + ) else: event_filter = and_( event_filter, Event.admin_unit_id == params.admin_unit_id @@ -271,21 +279,9 @@ def get_event_dates_query(params: EventSearchParams): .filter(event_filter) ) - if params.sort == "-rating": - if admin_unit_reference: - result = result.order_by( - case( - ( - admin_unit_reference.rating.isnot(None), - admin_unit_reference.rating, - ), - else_=Event.rating, - ).desc() - ) - else: - result = result.order_by(Event.rating.desc()) - + result = fill_event_query_order(result, admin_unit_reference, params) result = result.order_by(EventDate.start) + return result @@ -395,17 +391,38 @@ def get_events_query(params: EventSearchParams): isouter=True, ) - result = ( - result.options( - contains_eager(Event.event_place).contains_eager(EventPlace.location), - joinedload(Event.categories), - joinedload(Event.organizer), - joinedload(Event.photo), - joinedload(Event.admin_unit), - ) - .filter(event_filter) - .order_by(Event.min_start) - ) + result = result.options( + contains_eager(Event.event_place).contains_eager(EventPlace.location), + joinedload(Event.categories), + joinedload(Event.organizer), + joinedload(Event.photo), + joinedload(Event.admin_unit), + ).filter(event_filter) + + result = fill_event_query_order(result, admin_unit_reference, params) + result = result.order_by(Event.min_start) + + return result + + +def fill_event_query_order(result, admin_unit_reference, params: EventSearchParams): + result = params.get_trackable_order_by(result, Event) + + if params.sort == "-rating": + if admin_unit_reference: + result = result.order_by( + case( + ( + admin_unit_reference.rating.isnot(None), + admin_unit_reference.rating, + ), + else_=Event.rating, + ).desc() + ) + else: + result = result.order_by(Event.rating.desc()) + elif params.sort == "-reference_created_at" and admin_unit_reference: + result = result.order_by(admin_unit_reference.created_at.desc()) return result @@ -551,8 +568,8 @@ def populate_ical_event_with_event( if model_event.created_at: ical_event.add("dtstamp", model_event.created_at) - if model_event.updated_at: - ical_event.add("last-modified", model_event.updated_at) + if model_event.last_modified_at: + ical_event.add("last-modified", model_event.last_modified_at) if model_event.status and model_event.status == EventStatus.cancelled: ical_event.add("status", "CANCELLED") diff --git a/project/services/organizer.py b/project/services/organizer.py index f4bdba6..a7bc249 100644 --- a/project/services/organizer.py +++ b/project/services/organizer.py @@ -27,7 +27,7 @@ def create_ical_events_for_organizer( from project.dateutils import get_today from project.services.event import create_ical_events_for_search - from project.services.event_search import EventSearchParams + from project.services.search_params import EventSearchParams params = EventSearchParams() params.date_from = get_today() - relativedelta(months=1) diff --git a/project/services/reference.py b/project/services/reference.py index 1302c64..d0b760b 100644 --- a/project/services/reference.py +++ b/project/services/reference.py @@ -10,6 +10,7 @@ from project.models import ( EventReferenceRequestReviewStatus, ) from project.models.admin_unit import AdminUnit +from project.services.search_params import EventReferenceSearchParams def create_event_reference_for_request(request): @@ -36,12 +37,24 @@ def upsert_event_reference(event_id: int, admin_unit_id: int): return result -def get_reference_incoming_query(admin_unit): - return EventReference.query.filter(EventReference.admin_unit_id == admin_unit.id) +def get_reference_incoming_query(params: EventReferenceSearchParams): + result = EventReference.query + + if params.admin_unit_id: + result = result.filter(EventReference.admin_unit_id == params.admin_unit_id) + + result = params.get_trackable_query(result, EventReference) + result = params.get_trackable_order_by(result, EventReference) + result = result.order_by(EventReference.created_at.desc()) + return result def get_reference_outgoing_query(admin_unit): - return EventReference.query.join(Event).filter(Event.admin_unit_id == admin_unit.id) + return ( + EventReference.query.join(Event) + .filter(Event.admin_unit_id == admin_unit.id) + .order_by(EventReference.created_at.desc()) + ) def get_reference_requests_incoming_query(admin_unit): diff --git a/project/services/event_search.py b/project/services/search_params.py similarity index 62% rename from project/services/event_search.py rename to project/services/search_params.py index 4479f62..f849d09 100644 --- a/project/services/event_search.py +++ b/project/services/search_params.py @@ -1,5 +1,8 @@ +from typing import Type + from dateutil.relativedelta import relativedelta from flask import request +from sqlalchemy import and_ from project.dateutils import ( date_set_end_of_day, @@ -7,10 +10,100 @@ from project.dateutils import ( form_input_to_date, get_today, ) +from project.models.trackable_mixin import TrackableMixin -class EventSearchParams(object): +class BaseSearchParams(object): def __init__(self): + self.sort = None + + def load_from_request(self, **kwargs): + self.sort = kwargs.get("sort", self.sort) + + +class TrackableSearchParams(BaseSearchParams): + def __init__(self): + super().__init__() + self.created_at_from = None + self.created_at_to = None + + def load_from_request(self, **kwargs): + super().load_from_request(**kwargs) + + self.created_at_from = kwargs.get("created_at_from", self.created_at_from) + self.created_at_to = kwargs.get("created_at_to", self.created_at_to) + + def get_trackable_query(self, query, klass: Type[TrackableMixin]): + filter = self.fill_trackable_filter(1 == 1, klass) + return query.filter(filter) + + def fill_trackable_filter(self, filter, klass: Type[TrackableMixin]): + if self.created_at_from: + filter = and_(filter, klass.created_at >= self.created_at_from) + + if self.created_at_to: + filter = and_(filter, klass.created_at < self.created_at_to) + + return filter + + def get_trackable_order_by(self, query, klass: Type[TrackableMixin]): + if self.sort == "-created_at": + query = query.order_by(klass.created_at.desc()) + elif self.sort == "-updated_at": + query = query.order_by(klass.updated_at.desc().nulls_last()) + elif self.sort == "-last_modified_at": + query = query.order_by(klass.last_modified_at.desc()) + + return query + + +class EventReferenceSearchParams(TrackableSearchParams): + def __init__(self): + super().__init__() + self.admin_unit_id = None + + +class AdminUnitSearchParams(TrackableSearchParams): + def __init__(self): + super().__init__() + self.keyword = None + self.include_unverified = False + self.only_verifier = False + self.reference_request_for_admin_unit_id = None + + def load_from_request(self, **kwargs): + super().load_from_request(**kwargs) + + self.keyword = kwargs.get("keyword", self.keyword) + + +class OrganizerSearchParams(TrackableSearchParams): + def __init__(self): + super().__init__() + self.admin_unit_id = None + self.name = None + + def load_from_request(self, **kwargs): + super().load_from_request(**kwargs) + + self.name = kwargs.get("name", self.name) + + +class EventPlaceSearchParams(TrackableSearchParams): + def __init__(self): + super().__init__() + self.admin_unit_id = None + self.name = None + + def load_from_request(self, **kwargs): + super().load_from_request(**kwargs) + + self.name = kwargs.get("name", self.name) + + +class EventSearchParams(TrackableSearchParams): + def __init__(self): + super().__init__() self._date_from = None self._date_to = None self._date_from_str = None @@ -18,6 +111,7 @@ class EventSearchParams(object): self._coordinate = None self.admin_unit_id = None self.include_admin_unit_references = None + self.admin_unit_references_only = None self.can_read_private_events = None self.can_read_planned_events = None self.keyword = None @@ -29,7 +123,6 @@ class EventSearchParams(object): self.event_place_id = None self.event_list_id = None self.weekday = None - self.sort = None self.status = None self.public_status = None self.favored_by_user_id = None @@ -112,7 +205,7 @@ class EventSearchParams(object): return None def load_bool_param(self, param: str): - return request.args[param] == "y" + return request.args[param].lower() in ("true", "t", "yes", "y", "on", "1") def load_status_list_param(self): stati = self.load_list_param("status") @@ -146,7 +239,9 @@ class EventSearchParams(object): return result - def load_from_request(self): + def load_from_request(self, **kwargs): + super().load_from_request(**kwargs) + if "date_from" in request.args: self.date_from_str = request.args["date_from"] @@ -183,9 +278,6 @@ class EventSearchParams(object): if "postal_code" in request.args: self.postal_code = self.load_list_param("postal_code") - if "sort" in request.args: - self.sort = request.args["sort"] - if "organization_id" in request.args: self.admin_unit_id = request.args["organization_id"] @@ -200,3 +292,13 @@ class EventSearchParams(object): if "exclude_recurring" in request.args: self.exclude_recurring = self.load_bool_param("exclude_recurring") + + if "include_organization_references" in request.args: + self.include_admin_unit_references = self.load_bool_param( + "include_organization_references" + ) + + if "organization_references_only" in request.args: + self.admin_unit_references_only = self.load_bool_param( + "organization_references_only" + ) diff --git a/project/services/seo.py b/project/services/seo.py index 155faca..c63c97c 100644 --- a/project/services/seo.py +++ b/project/services/seo.py @@ -24,7 +24,7 @@ def generate_sitemap(pinggoogle: bool): today = get_today() events = ( Event.query.join(Event.admin_unit) - .options(load_only(Event.id, Event.updated_at)) + .options(load_only(Event.id, Event.last_modified_at)) .filter(Event.dates.any(EventDate.start >= today)) .filter( and_( @@ -38,7 +38,11 @@ def generate_sitemap(pinggoogle: bool): for event in events: loc = url_for("event", event_id=event.id) - lastmod = event.updated_at.strftime("%Y-%m-%d") if event.updated_at else None + lastmod = ( + event.last_modified_at.strftime("%Y-%m-%d") + if event.last_modified_at + else None + ) lastmod_tag = f"{lastmod}" if lastmod else "" buf.write(f"{loc}{lastmod_tag}") diff --git a/project/templates/_macros.html b/project/templates/_macros.html index 4c0f2d9..361c0f3 100644 --- a/project/templates/_macros.html +++ b/project/templates/_macros.html @@ -366,7 +366,6 @@ {% macro render_audit(tracking_mixing, show_user=False) %} {% set created_at = tracking_mixing.created_at | datetimeformat('short') %} - {% set updated_at = tracking_mixing.updated_at | datetimeformat('short') %} {% if show_user %} {{ _('Created at %(created_at)s by %(created_by)s.', created_at=created_at, created_by=tracking_mixing.created_by.email) }} @@ -374,11 +373,14 @@ {{ _('Created at %(created_at)s.', created_at=created_at) }} {% endif %} - {% if created_at != updated_at %} - {% if show_user %} - {{ _('Last updated at %(updated_at)s by %(updated_by)s.', updated_at=updated_at, updated_by=tracking_mixing.updated_by.email) }} - {% else %} - {{ _('Last updated at %(updated_at)s.', updated_at=updated_at) }} + {% if tracking_mixing.updated_at %} + {% set updated_at = tracking_mixing.updated_at | datetimeformat('short') %} + {% if created_at != updated_at %} + {% if show_user %} + {{ _('Last updated at %(updated_at)s by %(updated_by)s.', updated_at=updated_at, updated_by=tracking_mixing.updated_by.email) }} + {% else %} + {{ _('Last updated at %(updated_at)s.', updated_at=updated_at) }} + {% endif %} {% endif %} {% endif %} {% endmacro %} diff --git a/project/views/event_date.py b/project/views/event_date.py index 9080811..0e29eb8 100644 --- a/project/views/event_date.py +++ b/project/views/event_date.py @@ -13,7 +13,7 @@ from project.services.event import ( get_meta_data, get_upcoming_event_dates, ) -from project.services.event_search import EventSearchParams +from project.services.search_params import EventSearchParams from project.views.event import get_event_category_choices, get_user_rights from project.views.utils import ( flash_errors, diff --git a/project/views/image.py b/project/views/image.py index b295b78..cd99dd8 100644 --- a/project/views/image.py +++ b/project/views/image.py @@ -14,7 +14,7 @@ from project.utils import make_dir @app.route("/image//") def image(id, hash=None): image = Image.query.options( - load_only(Image.id, Image.encoding_format, Image.updated_at) + load_only(Image.id, Image.encoding_format, Image.last_modified_at) ).get_or_404(id) # Dimensions diff --git a/project/views/manage.py b/project/views/manage.py index e6c037e..c24827c 100644 --- a/project/views/manage.py +++ b/project/views/manage.py @@ -43,8 +43,8 @@ from project.services.admin_unit import ( get_member_for_admin_unit_by_user_id, ) from project.services.event import get_events_query -from project.services.event_search import EventSearchParams from project.services.event_suggestion import get_event_reviews_query +from project.services.search_params import EventSearchParams from project.utils import get_place_str from project.views.event import get_event_category_choices from project.views.utils import ( diff --git a/project/views/planning.py b/project/views/planning.py index 789ab57..14337ae 100644 --- a/project/views/planning.py +++ b/project/views/planning.py @@ -4,7 +4,7 @@ from flask_security import auth_required from project import app from project.access import can_use_planning from project.forms.planning import PlanningForm -from project.services.event_search import EventSearchParams +from project.services.search_params import EventSearchParams from project.views.event import get_event_category_choices from project.views.utils import permission_missing diff --git a/project/views/reference.py b/project/views/reference.py index b8e7e2f..a820894 100644 --- a/project/views/reference.py +++ b/project/views/reference.py @@ -2,7 +2,6 @@ from flask import abort, flash, redirect, render_template, url_for from flask_babel import gettext from flask_security import auth_required from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy.sql import desc from project import app, db from project.access import ( @@ -22,6 +21,7 @@ from project.services.reference import ( get_reference_incoming_query, get_reference_outgoing_query, ) +from project.services.search_params import EventReferenceSearchParams from project.views.utils import flash_errors, get_pagination_urls, handleSqlError @@ -112,11 +112,9 @@ def event_reference_update(id): @auth_required() def manage_admin_unit_references_incoming(id): admin_unit = get_admin_unit_for_manage_or_404(id) - references = ( - get_reference_incoming_query(admin_unit) - .order_by(desc(EventReference.created_at)) - .paginate() - ) + params = EventReferenceSearchParams() + params.admin_unit_id = admin_unit.id + references = get_reference_incoming_query(params).paginate() return render_template( "manage/references_incoming.html", @@ -130,11 +128,7 @@ def manage_admin_unit_references_incoming(id): @auth_required() def manage_admin_unit_references_outgoing(id): admin_unit = get_admin_unit_for_manage_or_404(id) - references = ( - get_reference_outgoing_query(admin_unit) - .order_by(desc(EventReference.created_at)) - .paginate() - ) + references = get_reference_outgoing_query(admin_unit).paginate() return render_template( "manage/references_outgoing.html", diff --git a/project/views/verification_request.py b/project/views/verification_request.py index cf46f2a..91c8792 100644 --- a/project/views/verification_request.py +++ b/project/views/verification_request.py @@ -15,6 +15,7 @@ from project.models import ( ) from project.models.admin_unit import AdminUnit from project.services.admin_unit import get_admin_unit_query +from project.services.search_params import AdminUnitSearchParams from project.services.verification import get_verification_requests_incoming_query from project.views.utils import ( flash_errors, @@ -79,7 +80,10 @@ def manage_admin_unit_verification_requests_outgoing(id): @manage_required("verification_request:create") def manage_admin_unit_verification_requests_outgoing_create_select(id): admin_unit = g.manage_admin_unit - admin_units = get_admin_unit_query(only_verifier=True).paginate() + + params = AdminUnitSearchParams() + params.only_verifier = True + admin_units = get_admin_unit_query(params).paginate() return render_template( "manage/verification_requests_outgoing_create_select.html", diff --git a/project/views/widget.py b/project/views/widget.py index 5b80092..6f677a2 100644 --- a/project/views/widget.py +++ b/project/views/widget.py @@ -19,9 +19,9 @@ from project.services.event import ( get_event_date_with_details_or_404, get_event_dates_query, ) -from project.services.event_search import EventSearchParams from project.services.event_suggestion import insert_event_suggestion from project.services.place import get_event_places +from project.services.search_params import EventSearchParams from project.views.event import get_event_category_choices from project.views.utils import ( flash_errors, diff --git a/tests/api/test_event_date.py b/tests/api/test_event_date.py index 4d34817..93c6b8d 100644 --- a/tests/api/test_event_date.py +++ b/tests/api/test_event_date.py @@ -61,6 +61,15 @@ def test_search(client, seeder: Seeder, utils: UtilActions, app, db): seeder.create_event(admin_unit_id, draft=True) seeder.create_event_unverified() + url = utils.get_url("api_v1_event_date_search", sort="-created_at") + response = utils.get_json_ok(url) + + url = utils.get_url("api_v1_event_date_search", sort="-updated_at") + response = utils.get_json_ok(url) + + url = utils.get_url("api_v1_event_date_search", sort="-last_modified_at") + response = utils.get_json_ok(url) + url = utils.get_url("api_v1_event_date_search", sort="-rating") response = utils.get_json_ok(url) assert len(response.json["items"]) == 1 @@ -84,6 +93,13 @@ def test_search(client, seeder: Seeder, utils: UtilActions, app, db): ) response = utils.get_json_ok(url) + url = utils.get_url( + "api_v1_event_date_search", + created_at_from="2023-07-07T00:00:00", + created_at_to="2023-07-08T00:00:00", + ) + response = utils.get_json_ok(url) + url = utils.get_url( "api_v1_event_date_search", coordinate="51.9077888,10.4333312", distance=500 ) @@ -139,6 +155,14 @@ def test_search(client, seeder: Seeder, utils: UtilActions, app, db): response = utils.get_json_ok(url) assert len(response.json["items"]) == 0 + seeder.create_any_reference(admin_unit_id) + url = utils.get_url( + "api_v1_event_date_search", + organization_id=admin_unit_id, + sort="-reference_created_at", + ) + response = utils.get_json_ok(url) + def test_search_public_status(client, seeder: Seeder, utils: UtilActions, app, db): user_id, admin_unit_id = seeder.setup_api_access(user_access=False) diff --git a/tests/api/test_organization.py b/tests/api/test_organization.py index aecc121..a8d3712 100644 --- a/tests/api/test_organization.py +++ b/tests/api/test_organization.py @@ -126,6 +126,21 @@ def test_event_search(client, seeder: Seeder, utils: UtilActions): or response.json["items"][1]["public_status"] == "draft" ) + seeder.create_any_reference(admin_unit_id) + url = utils.get_url( + "api_v1_organization_event_search", + id=admin_unit_id, + include_organization_references="yes", + ) + response = utils.get_json_ok(url) + + url = utils.get_url( + "api_v1_organization_event_search", + id=admin_unit_id, + organization_references_only="yes", + ) + response = utils.get_json_ok(url) + def test_organizers(client, seeder: Seeder, utils: UtilActions): user_id, admin_unit_id = seeder.setup_api_access(user_access=False) @@ -420,7 +435,14 @@ def test_references_incoming(client, seeder: Seeder, utils: UtilActions): url = utils.get_url( "api_v1_organization_incoming_event_reference_list", id=admin_unit_id, - name="crew", + ) + utils.get_json_ok(url) + + url = utils.get_url( + "api_v1_organization_incoming_event_reference_list", + id=admin_unit_id, + created_at_from="2023-07-07T00:00:00", + created_at_to="2023-07-08T00:00:00", ) utils.get_json_ok(url) diff --git a/tests/services/test_event.py b/tests/services/test_event.py index 1c5fd05..88f5cc8 100644 --- a/tests/services/test_event.py +++ b/tests/services/test_event.py @@ -199,7 +199,7 @@ def test_get_events_query(client, seeder, app): with app.app_context(): from project.services.event import get_events_query - from project.services.event_search import EventSearchParams + from project.services.search_params import EventSearchParams params = EventSearchParams() params.admin_unit_id = admin_unit_id @@ -253,7 +253,7 @@ def test_get_events_fulltext( with app.app_context(): from project.services.event import get_events_query - from project.services.event_search import EventSearchParams + from project.services.search_params import EventSearchParams params = EventSearchParams() params.keyword = keyword diff --git a/tests/services/test_event_search.py b/tests/services/test_event_search.py index ce5a408..1ab8358 100644 --- a/tests/services/test_event_search.py +++ b/tests/services/test_event_search.py @@ -1,6 +1,6 @@ def test_date_str(client, seeder, utils): from project.dateutils import create_berlin_date - from project.services.event_search import EventSearchParams + from project.services.search_params import EventSearchParams params = EventSearchParams() params.date_from = create_berlin_date(2030, 12, 30, 0)