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)