diff --git a/.dockerignore b/.dockerignore index 5be8957..b6db6ae 100644 --- a/.dockerignore +++ b/.dockerignore @@ -23,3 +23,4 @@ **/secrets.dev.yaml **/values.dev.yaml README.md +tmp diff --git a/README.md b/README.md index 9a60432..c6c31ea 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ Jobs that should run on a regular basis. ```sh flask event update-recurring-dates +flask dump all ``` ## Administration diff --git a/project/__init__.py b/project/__init__.py index d33b5a8..79acc89 100644 --- a/project/__init__.py +++ b/project/__init__.py @@ -15,6 +15,11 @@ from flask_restful import Api from apispec import APISpec from apispec.ext.marshmallow import MarshmallowPlugin from flask_apispec.extension import FlaskApiSpec +import pathlib +import logging + +logging.basicConfig() +logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO) # Create app app = Flask(__name__) @@ -39,6 +44,11 @@ app.config["SECURITY_PASSWORD_SALT"] = os.environ.get( "SECURITY_PASSWORD_SALT", "146585145368132386173505678016728509634" ) +# Temporary pathes +temp_path = os.path.join(app.root_path, "tmp") +dump_path = os.path.join(temp_path, "dump") +pathlib.Path(dump_path).mkdir(parents=True, exist_ok=True) + # i18n app.config["BABEL_DEFAULT_LOCALE"] = "de" app.config["BABEL_DEFAULT_TIMEZONE"] = "Europe/Berlin" @@ -55,9 +65,12 @@ app.config.update( { "APISPEC_SPEC": APISpec( title="Oveda API", - version="1.0.0", + version="0.1.0", plugins=[marshmallow_plugin], openapi_version="2.0", + info=dict( + description="This API provides endpoints to interact with the Oveda data. At the moment, there is no authorization neeeded." + ), ), } ) @@ -123,6 +136,7 @@ from project.views import ( event_date, event_place, event_suggestion, + dump, image, manage, organizer, @@ -140,6 +154,7 @@ import project.api # Command line import project.cli.event +import project.cli.dump import project.cli.user if __name__ == "__main__": # pragma: no cover diff --git a/project/api/__init__.py b/project/api/__init__.py index 3c2a12c..6df71ae 100644 --- a/project/api/__init__.py +++ b/project/api/__init__.py @@ -1,3 +1,6 @@ +from project import rest_api, api_docs + + def enum_to_properties(self, field, **kwargs): """ Add an OpenAPI extension for marshmallow_enum.EnumField instances @@ -9,6 +12,11 @@ def enum_to_properties(self, field, **kwargs): return {} +def add_api_resource(resource, url, endpoint): + rest_api.add_resource(resource, url, endpoint=endpoint) + api_docs.register(resource, endpoint=endpoint) + + from project import marshmallow_plugin marshmallow_plugin.converter.add_attribute_function(enum_to_properties) @@ -16,6 +24,8 @@ marshmallow_plugin.converter.add_attribute_function(enum_to_properties) import project.api.event.resources import project.api.event_category.resources import project.api.event_date.resources +import project.api.event_reference.resources +import project.api.dump.resources import project.api.image.resources import project.api.location.resources import project.api.organization.resources diff --git a/project/api/dump/__init__.py b/project/api/dump/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/api/dump/resources.py b/project/api/dump/resources.py new file mode 100644 index 0000000..00a0334 --- /dev/null +++ b/project/api/dump/resources.py @@ -0,0 +1,20 @@ +from project.api import add_api_resource +from flask_apispec import marshal_with, doc +from flask_apispec.views import MethodResource +from project.api.schemas import NoneSchema +from project.api.dump.schemas import DumpResponseSchema + + +class DumpResource(MethodResource): + @doc( + summary="Dump model definition", + description="Always returns 404 because the endpoint is just for response definition of the dump data file. Go to the developers page to learn how to download the dump data file.", + tags=["Dump"], + ) + @marshal_with(NoneSchema, 404) + @marshal_with(DumpResponseSchema, 200) + def get(self, **kwargs): + return None, 404 + + +add_api_resource(DumpResource, "/dump", "api_v1_dump") diff --git a/project/api/dump/schemas.py b/project/api/dump/schemas.py new file mode 100644 index 0000000..c78e973 --- /dev/null +++ b/project/api/dump/schemas.py @@ -0,0 +1,21 @@ +from project import marshmallow +from marshmallow import fields +from project.api.event.schemas import EventDumpSchema +from project.api.place.schemas import PlaceDumpSchema +from project.api.location.schemas import LocationDumpSchema +from project.api.event_category.schemas import EventCategoryDumpSchema +from project.api.organizer.schemas import OrganizerDumpSchema +from project.api.image.schemas import ImageDumpSchema +from project.api.organization.schemas import OrganizationDumpSchema +from project.api.event_reference.schemas import EventReferenceDumpSchema + + +class DumpResponseSchema(marshmallow.Schema): + events = fields.List(fields.Nested(EventDumpSchema)) + places = fields.List(fields.Nested(PlaceDumpSchema)) + locations = fields.List(fields.Nested(LocationDumpSchema)) + event_categories = fields.List(fields.Nested(EventCategoryDumpSchema)) + organizers = fields.List(fields.Nested(OrganizerDumpSchema)) + images = fields.List(fields.Nested(ImageDumpSchema)) + organizations = fields.List(fields.Nested(OrganizationDumpSchema)) + event_references = fields.List(fields.Nested(EventReferenceDumpSchema)) diff --git a/project/api/event/resources.py b/project/api/event/resources.py index 84bab88..4d39715 100644 --- a/project/api/event/resources.py +++ b/project/api/event/resources.py @@ -1,4 +1,4 @@ -from project import rest_api, api_docs +from project.api import add_api_resource from flask_apispec import marshal_with, doc, use_kwargs from flask_apispec.views import MethodResource from project.api.event.schemas import ( @@ -18,7 +18,7 @@ from project.services.event_search import EventSearchParams class EventListResource(MethodResource): - @doc(tags=["Events"]) + @doc(summary="List events", tags=["Events"]) @use_kwargs(EventListRequestSchema, location=("query")) @marshal_with(EventListResponseSchema) def get(self, **kwargs): @@ -27,14 +27,14 @@ class EventListResource(MethodResource): class EventResource(MethodResource): - @doc(tags=["Events"]) + @doc(summary="Get event", tags=["Events"]) @marshal_with(EventSchema) def get(self, id): return Event.query.get_or_404(id) class EventDatesResource(MethodResource): - @doc(tags=["Events", "Event Dates"]) + @doc(summary="List dates for event", tags=["Events", "Event Dates"]) @use_kwargs(EventDateListRequestSchema, location=("query")) @marshal_with(EventDateListResponseSchema) def get(self, id): @@ -43,7 +43,7 @@ class EventDatesResource(MethodResource): class EventSearchResource(MethodResource): - @doc(tags=["Events"]) + @doc(summary="Search for events", tags=["Events"]) @use_kwargs(EventSearchRequestSchema, location=("query")) @marshal_with(EventSearchResponseSchema) def get(self, **kwargs): @@ -53,14 +53,7 @@ class EventSearchResource(MethodResource): return pagination -rest_api.add_resource(EventListResource, "/events") -api_docs.register(EventListResource) - -rest_api.add_resource(EventResource, "/events/") -api_docs.register(EventResource) - -rest_api.add_resource(EventDatesResource, "/events//dates") -api_docs.register(EventDatesResource) - -rest_api.add_resource(EventSearchResource, "/events/search") -api_docs.register(EventSearchResource) +add_api_resource(EventListResource, "/events", "api_v1_event_list") +add_api_resource(EventResource, "/events/", "api_v1_event") +add_api_resource(EventDatesResource, "/events//dates", "api_v1_event_dates") +add_api_resource(EventSearchResource, "/events/search", "api_v1_event_search") diff --git a/project/api/event/schemas.py b/project/api/event/schemas.py index 7c3fb71..aba641b 100644 --- a/project/api/event/schemas.py +++ b/project/api/event/schemas.py @@ -12,26 +12,25 @@ from project.api.organization.schemas import OrganizationRefSchema from project.api.organizer.schemas import OrganizerRefSchema from project.api.image.schemas import ImageRefSchema from project.api.place.schemas import PlaceRefSchema, PlaceSearchItemSchema -from project.api.event_category.schemas import EventCategoryRefSchema +from project.api.event_category.schemas import ( + EventCategoryRefSchema, + EventCategoryIdSchema, +) -class EventSchema(marshmallow.SQLAlchemySchema): +class EventBaseSchema(marshmallow.SQLAlchemySchema): class Meta: model = Event id = marshmallow.auto_field() created_at = marshmallow.auto_field() updated_at = marshmallow.auto_field() - organization = fields.Nested(OrganizationRefSchema, attribute="admin_unit") - organizer = fields.Nested(OrganizerRefSchema) - place = fields.Nested(PlaceRefSchema, attribute="event_place") + name = marshmallow.auto_field() description = marshmallow.auto_field() external_link = marshmallow.auto_field() ticket_link = marshmallow.auto_field() - photo = fields.Nested(ImageRefSchema) - categories = fields.List(fields.Nested(EventCategoryRefSchema)) tags = marshmallow.auto_field() kid_friendly = marshmallow.auto_field() accessible_for_free = marshmallow.auto_field() @@ -52,12 +51,29 @@ class EventSchema(marshmallow.SQLAlchemySchema): end = marshmallow.auto_field() +class EventSchema(EventBaseSchema): + organization = fields.Nested(OrganizationRefSchema, attribute="admin_unit") + organizer = fields.Nested(OrganizerRefSchema) + place = fields.Nested(PlaceRefSchema, attribute="event_place") + photo = fields.Nested(ImageRefSchema) + categories = fields.List(fields.Nested(EventCategoryRefSchema)) + + +class EventDumpSchema(EventBaseSchema): + organization_id = fields.Int(attribute="admin_unit_id") + organizer_id = fields.Int() + place_id = fields.Int(attribute="event_place_id") + photo_id = fields.Int() + category_ids = fields.Pluck( + EventCategoryIdSchema, "id", many=True, attribute="categories" + ) + + class EventRefSchema(marshmallow.SQLAlchemySchema): class Meta: model = Event id = marshmallow.auto_field() - href = marshmallow.URLFor("eventresource", values=dict(id="")) name = marshmallow.auto_field() @@ -73,6 +89,7 @@ class EventSearchItemSchema(EventRefSchema): place = fields.Nested(PlaceSearchItemSchema, attribute="event_place") status = EnumField(EventStatus) organizer = fields.Nested(OrganizerRefSchema) + organization = fields.Nested(OrganizationRefSchema, attribute="admin_unit") categories = fields.List(fields.Nested(EventCategoryRefSchema)) diff --git a/project/api/event_category/resources.py b/project/api/event_category/resources.py index 20f1442..272e0be 100644 --- a/project/api/event_category/resources.py +++ b/project/api/event_category/resources.py @@ -1,4 +1,4 @@ -from project import rest_api, api_docs +from project.api import add_api_resource from flask_apispec import marshal_with, doc, use_kwargs from flask_apispec.views import MethodResource from project.api.event_category.schemas import ( @@ -9,7 +9,7 @@ from project.models import EventCategory class EventCategoryListResource(MethodResource): - @doc(tags=["Event Categories"]) + @doc(summary="List event categories", tags=["Event Categories"]) @use_kwargs(EventCategoryListRequestSchema, location=("query")) @marshal_with(EventCategoryListResponseSchema) def get(self, **kwargs): @@ -17,5 +17,6 @@ class EventCategoryListResource(MethodResource): return pagination -rest_api.add_resource(EventCategoryListResource, "/event_categories") -api_docs.register(EventCategoryListResource) +add_api_resource( + EventCategoryListResource, "/event-categories", "api_v1_event_category_list" +) diff --git a/project/api/event_category/schemas.py b/project/api/event_category/schemas.py index fdc5d34..a354a06 100644 --- a/project/api/event_category/schemas.py +++ b/project/api/event_category/schemas.py @@ -4,14 +4,21 @@ from project.models import EventCategory from project.api.schemas import PaginationRequestSchema, PaginationResponseSchema -class EventCategoryRefSchema(marshmallow.SQLAlchemySchema): +class EventCategoryIdSchema(marshmallow.SQLAlchemySchema): class Meta: model = EventCategory id = marshmallow.auto_field() + + +class EventCategoryRefSchema(EventCategoryIdSchema): name = marshmallow.auto_field() +class EventCategoryDumpSchema(EventCategoryRefSchema): + pass + + class EventCategoryListRequestSchema(PaginationRequestSchema): pass diff --git a/project/api/event_date/resources.py b/project/api/event_date/resources.py index 6ef1dc9..05bd9bc 100644 --- a/project/api/event_date/resources.py +++ b/project/api/event_date/resources.py @@ -1,4 +1,4 @@ -from project import rest_api, api_docs +from project.api import add_api_resource from flask_apispec import marshal_with, doc, use_kwargs from flask_apispec.views import MethodResource from project.api.event_date.schemas import ( @@ -14,7 +14,7 @@ from project.services.event_search import EventSearchParams class EventDateListResource(MethodResource): - @doc(tags=["Event Dates"]) + @doc(summary="List event dates", tags=["Event Dates"]) @use_kwargs(EventDateListRequestSchema, location=("query")) @marshal_with(EventDateListResponseSchema) def get(self, **kwargs): @@ -23,14 +23,14 @@ class EventDateListResource(MethodResource): class EventDateResource(MethodResource): - @doc(tags=["Event Dates"]) + @doc(summary="Get event date", tags=["Event Dates"]) @marshal_with(EventDateSchema) def get(self, id): return EventDate.query.get_or_404(id) class EventDateSearchResource(MethodResource): - @doc(tags=["Event Dates"]) + @doc(summary="Search for event dates", tags=["Event Dates"]) @use_kwargs(EventDateSearchRequestSchema, location=("query")) @marshal_with(EventDateSearchResponseSchema) def get(self, **kwargs): @@ -40,11 +40,8 @@ class EventDateSearchResource(MethodResource): return pagination -rest_api.add_resource(EventDateListResource, "/event_dates") -api_docs.register(EventDateListResource) - -rest_api.add_resource(EventDateResource, "/event_dates/") -api_docs.register(EventDateResource) - -rest_api.add_resource(EventDateSearchResource, "/event_dates/search") -api_docs.register(EventDateSearchResource) +add_api_resource(EventDateListResource, "/event-dates", "api_v1_event_date_list") +add_api_resource(EventDateResource, "/event-dates/", "api_v1_event_date") +add_api_resource( + EventDateSearchResource, "/event-dates/search", "api_v1_event_date_search" +) diff --git a/project/api/event_date/schemas.py b/project/api/event_date/schemas.py index b8268eb..159972f 100644 --- a/project/api/event_date/schemas.py +++ b/project/api/event_date/schemas.py @@ -24,7 +24,6 @@ class EventDateRefSchema(marshmallow.SQLAlchemySchema): model = EventDate id = marshmallow.auto_field() - href = marshmallow.URLFor("eventdateresource", values=dict(id="")) start = marshmallow.auto_field() diff --git a/project/api/event_reference/__init__.py b/project/api/event_reference/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/api/event_reference/resources.py b/project/api/event_reference/resources.py new file mode 100644 index 0000000..2326151 --- /dev/null +++ b/project/api/event_reference/resources.py @@ -0,0 +1,17 @@ +from project.api import add_api_resource +from flask_apispec import marshal_with, doc +from flask_apispec.views import MethodResource +from project.api.event_reference.schemas import EventReferenceSchema +from project.models import EventReference + + +class EventReferenceResource(MethodResource): + @doc(summary="Get event reference", tags=["Event References"]) + @marshal_with(EventReferenceSchema) + def get(self, id): + return EventReference.query.get_or_404(id) + + +add_api_resource( + EventReferenceResource, "/event-references/", "api_v1_event_reference" +) diff --git a/project/api/event_reference/schemas.py b/project/api/event_reference/schemas.py new file mode 100644 index 0000000..14cdf20 --- /dev/null +++ b/project/api/event_reference/schemas.py @@ -0,0 +1,38 @@ +from marshmallow import fields +from project import marshmallow +from project.models import EventReference +from project.api.schemas import PaginationRequestSchema, PaginationResponseSchema +from project.api.event.schemas import EventRefSchema +from project.api.organization.schemas import OrganizationRefSchema + + +class EventReferenceIdSchema(marshmallow.SQLAlchemySchema): + class Meta: + model = EventReference + + id = marshmallow.auto_field() + + +class EventReferenceRefSchema(EventReferenceIdSchema): + event = fields.Nested(EventRefSchema) + + +class EventReferenceSchema(EventReferenceIdSchema): + event = fields.Nested(EventRefSchema) + organization = fields.Nested(OrganizationRefSchema, attribute="admin_unit") + + +class EventReferenceDumpSchema(EventReferenceIdSchema): + event_id = marshmallow.auto_field() + organization_id = fields.Int(attribute="admin_unit_id") + + +class EventReferenceListRequestSchema(PaginationRequestSchema): + pass + + +class EventReferenceListResponseSchema(PaginationResponseSchema): + items = fields.List( + fields.Nested(EventReferenceRefSchema), + metadata={"description": "Event references"}, + ) diff --git a/project/api/image/resources.py b/project/api/image/resources.py index 864bd2d..b8e6ab9 100644 --- a/project/api/image/resources.py +++ b/project/api/image/resources.py @@ -1,16 +1,15 @@ -from project import rest_api, api_docs +from project.api import add_api_resource from flask_apispec import marshal_with, doc from flask_apispec.views import MethodResource from project.api.image.schemas import ImageSchema -from project.models import AdminUnit +from project.models import Image class ImageResource(MethodResource): - @doc(tags=["Images"]) + @doc(summary="Get image", tags=["Images"]) @marshal_with(ImageSchema) def get(self, id): - return AdminUnit.query.get_or_404(id) + return Image.query.get_or_404(id) -rest_api.add_resource(ImageResource, "/images/") -api_docs.register(ImageResource) +add_api_resource(ImageResource, "/images/", "api_v1_image") diff --git a/project/api/image/schemas.py b/project/api/image/schemas.py index 608d5d6..8d65f36 100644 --- a/project/api/image/schemas.py +++ b/project/api/image/schemas.py @@ -2,24 +2,26 @@ from project import marshmallow from project.models import Image -class ImageSchema(marshmallow.SQLAlchemySchema): +class ImageIdSchema(marshmallow.SQLAlchemySchema): class Meta: model = Image id = marshmallow.auto_field() + + +class ImageBaseSchema(ImageIdSchema): created_at = marshmallow.auto_field() updated_at = marshmallow.auto_field() - image_url = marshmallow.URLFor("image", values=dict(id="")) copyright_text = marshmallow.auto_field() -class ImageRefSchema(marshmallow.SQLAlchemySchema): - class Meta: - model = Image - - id = marshmallow.auto_field() +class ImageSchema(ImageBaseSchema): + image_url = marshmallow.URLFor("image", values=dict(id="")) + + +class ImageDumpSchema(ImageBaseSchema): + pass + + +class ImageRefSchema(ImageIdSchema): image_url = marshmallow.URLFor("image", values=dict(id="")) - href = marshmallow.URLFor( - "imageresource", - values=dict(id=""), - ) diff --git a/project/api/location/resources.py b/project/api/location/resources.py index 2f9a3a0..101d771 100644 --- a/project/api/location/resources.py +++ b/project/api/location/resources.py @@ -1,4 +1,4 @@ -from project import rest_api, api_docs +from project.api import add_api_resource from flask_apispec import marshal_with, doc from flask_apispec.views import MethodResource from project.api.location.schemas import LocationSchema @@ -6,11 +6,10 @@ from project.models import Location class LocationResource(MethodResource): - @doc(tags=["Locations"]) + @doc(summary="Get location", tags=["Locations"]) @marshal_with(LocationSchema) def get(self, id): return Location.query.get_or_404(id) -rest_api.add_resource(LocationResource, "/locations/") -api_docs.register(LocationResource) +add_api_resource(LocationResource, "/locations/", "api_v1_location") diff --git a/project/api/location/schemas.py b/project/api/location/schemas.py index d94a1c3..c16cb89 100644 --- a/project/api/location/schemas.py +++ b/project/api/location/schemas.py @@ -3,11 +3,14 @@ from project import marshmallow from project.models import Location -class LocationSchema(marshmallow.SQLAlchemySchema): +class LocationIdSchema(marshmallow.SQLAlchemySchema): class Meta: model = Location id = marshmallow.auto_field() + + +class LocationSchema(LocationIdSchema): created_at = marshmallow.auto_field() updated_at = marshmallow.auto_field() street = marshmallow.auto_field() @@ -19,15 +22,12 @@ class LocationSchema(marshmallow.SQLAlchemySchema): latitude = fields.Str() -class LocationRefSchema(marshmallow.SQLAlchemySchema): - class Meta: - model = Location +class LocationDumpSchema(LocationSchema): + pass - id = marshmallow.auto_field() - href = marshmallow.URLFor( - "locationresource", - values=dict(id=""), - ) + +class LocationRefSchema(LocationIdSchema): + pass class LocationSearchItemSchema(LocationRefSchema): diff --git a/project/api/organization/resources.py b/project/api/organization/resources.py index 09ef195..698d7c5 100644 --- a/project/api/organization/resources.py +++ b/project/api/organization/resources.py @@ -1,4 +1,4 @@ -from project import rest_api, api_docs +from project.api import add_api_resource from flask_apispec import marshal_with, doc, use_kwargs from flask_apispec.views import MethodResource from project.api.organization.schemas import ( @@ -19,6 +19,14 @@ from project.api.organizer.schemas import ( OrganizerListRequestSchema, OrganizerListResponseSchema, ) +from project.api.event_reference.schemas import ( + EventReferenceListRequestSchema, + EventReferenceListResponseSchema, +) +from project.services.reference import ( + get_reference_incoming_query, + get_reference_outgoing_query, +) from project.api.place.schemas import PlaceListRequestSchema, PlaceListResponseSchema from project.services.event import get_event_dates_query, get_events_query from project.services.event_search import EventSearchParams @@ -30,21 +38,18 @@ from project.services.admin_unit import ( class OrganizationResource(MethodResource): - @doc(tags=["Organizations"]) + @doc(summary="Get organization", tags=["Organizations"]) @marshal_with(OrganizationSchema) def get(self, id): return AdminUnit.query.get_or_404(id) -class OrganizationByShortNameResource(MethodResource): - @doc(tags=["Organizations"]) - @marshal_with(OrganizationSchema) - def get(self, short_name): - return AdminUnit.query.filter(AdminUnit.short_name == short_name).first_or_404() - - class OrganizationEventDateSearchResource(MethodResource): - @doc(tags=["Organizations", "Event Dates"]) + @doc( + summary="Search for event dates of organization", + description="Includes events that organization is referencing.", + tags=["Organizations", "Event Dates"], + ) @use_kwargs(EventDateSearchRequestSchema, location=("query")) @marshal_with(EventDateSearchResponseSchema) def get(self, id, **kwargs): @@ -59,7 +64,7 @@ class OrganizationEventDateSearchResource(MethodResource): class OrganizationEventSearchResource(MethodResource): - @doc(tags=["Organizations", "Events"]) + @doc(summary="Search for events of organization", tags=["Organizations", "Events"]) @use_kwargs(EventSearchRequestSchema, location=("query")) @marshal_with(EventSearchResponseSchema) def get(self, id, **kwargs): @@ -74,7 +79,7 @@ class OrganizationEventSearchResource(MethodResource): class OrganizationListResource(MethodResource): - @doc(tags=["Organizations"]) + @doc(summary="List organizations", tags=["Organizations"]) @use_kwargs(OrganizationListRequestSchema, location=("query")) @marshal_with(OrganizationListResponseSchema) def get(self, **kwargs): @@ -84,7 +89,9 @@ class OrganizationListResource(MethodResource): class OrganizationOrganizerListResource(MethodResource): - @doc(tags=["Organizations", "Organizers"]) + @doc( + summary="List organizers of organization", tags=["Organizations", "Organizers"] + ) @use_kwargs(OrganizerListRequestSchema, location=("query")) @marshal_with(OrganizerListResponseSchema) def get(self, id, **kwargs): @@ -96,7 +103,7 @@ class OrganizationOrganizerListResource(MethodResource): class OrganizationPlaceListResource(MethodResource): - @doc(tags=["Organizations", "Places"]) + @doc(summary="List places of organization", tags=["Organizations", "Places"]) @use_kwargs(PlaceListRequestSchema, location=("query")) @marshal_with(PlaceListResponseSchema) def get(self, id, **kwargs): @@ -107,35 +114,63 @@ class OrganizationPlaceListResource(MethodResource): return pagination -rest_api.add_resource(OrganizationResource, "/organizations/") -api_docs.register(OrganizationResource) +class OrganizationIncomingEventReferenceListResource(MethodResource): + @doc( + summary="List incoming event references of organization", + tags=["Organizations", "Event References"], + ) + @use_kwargs(EventReferenceListRequestSchema, location=("query")) + @marshal_with(EventReferenceListResponseSchema) + def get(self, id, **kwargs): + admin_unit = AdminUnit.query.get_or_404(id) -rest_api.add_resource( - OrganizationByShortNameResource, "/organizations/" -) -api_docs.register(OrganizationByShortNameResource) + pagination = get_reference_incoming_query(admin_unit).paginate() + return pagination -rest_api.add_resource( + +class OrganizationOutgoingEventReferenceListResource(MethodResource): + @doc( + summary="List outgoing event references of organization", + tags=["Organizations", "Event References"], + ) + @use_kwargs(EventReferenceListRequestSchema, location=("query")) + @marshal_with(EventReferenceListResponseSchema) + def get(self, id, **kwargs): + admin_unit = AdminUnit.query.get_or_404(id) + + pagination = get_reference_outgoing_query(admin_unit).paginate() + return pagination + + +add_api_resource(OrganizationResource, "/organizations/", "api_v1_organization") +add_api_resource( OrganizationEventDateSearchResource, - "/organizations//event_dates/search", + "/organizations//event-dates/search", + "api_v1_organization_event_date_search", ) -api_docs.register(OrganizationEventDateSearchResource) - -rest_api.add_resource( - OrganizationEventSearchResource, "/organizations//events/search" +add_api_resource( + OrganizationEventSearchResource, + "/organizations//events/search", + "api_v1_organization_event_search", ) -api_docs.register(OrganizationEventSearchResource) - -rest_api.add_resource(OrganizationListResource, "/organizations") -api_docs.register(OrganizationListResource) - -rest_api.add_resource( - OrganizationOrganizerListResource, "/organizations//organizers" +add_api_resource(OrganizationListResource, "/organizations", "api_v1_organization_list") +add_api_resource( + OrganizationOrganizerListResource, + "/organizations//organizers", + "api_v1_organization_organizer_list", ) -api_docs.register(OrganizationOrganizerListResource) - -rest_api.add_resource( +add_api_resource( OrganizationPlaceListResource, "/organizations//places", + "api_v1_organization_place_list", +) +add_api_resource( + OrganizationIncomingEventReferenceListResource, + "/organizations//event-references/incoming", + "api_v1_organization_incoming_event_reference_list", +) +add_api_resource( + OrganizationOutgoingEventReferenceListResource, + "/organizations//event-references/outgoing", + "api_v1_organization_outgoing_event_reference_list", ) -api_docs.register(OrganizationPlaceListResource) diff --git a/project/api/organization/schemas.py b/project/api/organization/schemas.py index 05ed413..4cbea2d 100644 --- a/project/api/organization/schemas.py +++ b/project/api/organization/schemas.py @@ -6,30 +6,36 @@ from project.api.image.schemas import ImageRefSchema from project.api.schemas import PaginationRequestSchema, PaginationResponseSchema -class OrganizationSchema(marshmallow.SQLAlchemySchema): +class OrganizationIdSchema(marshmallow.SQLAlchemySchema): class Meta: model = AdminUnit id = marshmallow.auto_field() + + +class OrganizationBaseSchema(OrganizationIdSchema): created_at = marshmallow.auto_field() updated_at = marshmallow.auto_field() name = marshmallow.auto_field() short_name = marshmallow.auto_field() - location = fields.Nested(LocationRefSchema) - logo = fields.Nested(ImageRefSchema) url = marshmallow.auto_field() email = marshmallow.auto_field() phone = marshmallow.auto_field() fax = marshmallow.auto_field() -class OrganizationRefSchema(marshmallow.SQLAlchemySchema): - class Meta: - model = AdminUnit +class OrganizationSchema(OrganizationBaseSchema): + location = fields.Nested(LocationRefSchema) + logo = fields.Nested(ImageRefSchema) - id = marshmallow.auto_field() + +class OrganizationDumpSchema(OrganizationBaseSchema): + location_id = fields.Int() + logo_id = fields.Int() + + +class OrganizationRefSchema(OrganizationIdSchema): name = marshmallow.auto_field() - href = marshmallow.URLFor("organizationresource", values=dict(id="")) class OrganizationListRefSchema(OrganizationRefSchema): diff --git a/project/api/organizer/resources.py b/project/api/organizer/resources.py index 64d07c3..56d8170 100644 --- a/project/api/organizer/resources.py +++ b/project/api/organizer/resources.py @@ -1,4 +1,4 @@ -from project import rest_api, api_docs +from project.api import add_api_resource from flask_apispec import marshal_with, doc from flask_apispec.views import MethodResource from project.api.organizer.schemas import OrganizerSchema @@ -6,14 +6,14 @@ from project.models import EventOrganizer class OrganizerResource(MethodResource): - @doc(tags=["Organizers"]) + @doc(summary="Get organizer", tags=["Organizers"]) @marshal_with(OrganizerSchema) def get(self, id): return EventOrganizer.query.get_or_404(id) -rest_api.add_resource( +add_api_resource( OrganizerResource, "/organizers/", + "api_v1_organizer", ) -api_docs.register(OrganizerResource) diff --git a/project/api/organizer/schemas.py b/project/api/organizer/schemas.py index addbd06..bcbf222 100644 --- a/project/api/organizer/schemas.py +++ b/project/api/organizer/schemas.py @@ -7,11 +7,14 @@ from project.api.organization.schemas import OrganizationRefSchema from project.api.schemas import PaginationRequestSchema, PaginationResponseSchema -class OrganizerSchema(marshmallow.SQLAlchemySchema): +class OrganizerIdSchema(marshmallow.SQLAlchemySchema): class Meta: model = EventOrganizer id = marshmallow.auto_field() + + +class OrganizerBaseSchema(OrganizerIdSchema): created_at = marshmallow.auto_field() updated_at = marshmallow.auto_field() name = marshmallow.auto_field() @@ -19,21 +22,22 @@ class OrganizerSchema(marshmallow.SQLAlchemySchema): email = marshmallow.auto_field() phone = marshmallow.auto_field() fax = marshmallow.auto_field() + + +class OrganizerSchema(OrganizerBaseSchema): location = fields.Nested(LocationRefSchema) logo = fields.Nested(ImageRefSchema) organization = fields.Nested(OrganizationRefSchema, attribute="adminunit") -class OrganizerRefSchema(marshmallow.SQLAlchemySchema): - class Meta: - model = EventOrganizer +class OrganizerDumpSchema(OrganizerBaseSchema): + location_id = fields.Int() + logo_id = fields.Int() + organization_id = fields.Int(attribute="admin_unit_id") - id = marshmallow.auto_field() + +class OrganizerRefSchema(OrganizerIdSchema): name = marshmallow.auto_field() - href = marshmallow.URLFor( - "organizerresource", - values=dict(id=""), - ) class OrganizerListRequestSchema(PaginationRequestSchema): diff --git a/project/api/place/resources.py b/project/api/place/resources.py index 2d46f3e..6efb627 100644 --- a/project/api/place/resources.py +++ b/project/api/place/resources.py @@ -1,4 +1,4 @@ -from project import rest_api, api_docs +from project.api import add_api_resource from flask_apispec import marshal_with, doc from flask_apispec.views import MethodResource from project.api.place.schemas import PlaceSchema @@ -6,14 +6,10 @@ from project.models import EventPlace class PlaceResource(MethodResource): - @doc(tags=["Places"]) + @doc(summary="Get place", tags=["Places"]) @marshal_with(PlaceSchema) def get(self, id): return EventPlace.query.get_or_404(id) -rest_api.add_resource( - PlaceResource, - "/places/", -) -api_docs.register(PlaceResource) +add_api_resource(PlaceResource, "/places/", "api_v1_place") diff --git a/project/api/place/schemas.py b/project/api/place/schemas.py index d9ac1e5..a862374 100644 --- a/project/api/place/schemas.py +++ b/project/api/place/schemas.py @@ -7,31 +7,35 @@ from project.api.organization.schemas import OrganizationRefSchema from project.api.schemas import PaginationRequestSchema, PaginationResponseSchema -class PlaceSchema(marshmallow.SQLAlchemySchema): +class PlaceIdSchema(marshmallow.SQLAlchemySchema): class Meta: model = EventPlace id = marshmallow.auto_field() + + +class PlaceBaseSchema(PlaceIdSchema): created_at = marshmallow.auto_field() updated_at = marshmallow.auto_field() name = marshmallow.auto_field() - location = fields.Nested(LocationRefSchema) - photo = fields.Nested(ImageRefSchema) url = marshmallow.auto_field() description = marshmallow.auto_field() + + +class PlaceSchema(PlaceBaseSchema): + location = fields.Nested(LocationRefSchema) + photo = fields.Nested(ImageRefSchema) organization = fields.Nested(OrganizationRefSchema, attribute="adminunit") -class PlaceRefSchema(marshmallow.SQLAlchemySchema): - class Meta: - model = EventPlace +class PlaceDumpSchema(PlaceBaseSchema): + location_id = fields.Int() + photo_id = fields.Int() + organization_id = fields.Int(attribute="admin_unit_id") - id = marshmallow.auto_field() + +class PlaceRefSchema(PlaceIdSchema): name = marshmallow.auto_field() - href = marshmallow.URLFor( - "placeresource", - values=dict(id=""), - ) class PlaceSearchItemSchema(PlaceRefSchema): diff --git a/project/api/schemas.py b/project/api/schemas.py index 2d49b0e..7b554a2 100644 --- a/project/api/schemas.py +++ b/project/api/schemas.py @@ -41,3 +41,7 @@ class PaginationResponseSchema(marshmallow.Schema): required=True, metadata={"description": "The total number of items matching the query"}, ) + + +class NoneSchema(marshmallow.Schema): + pass diff --git a/project/cli/dump.py b/project/cli/dump.py new file mode 100644 index 0000000..bf93fb9 --- /dev/null +++ b/project/cli/dump.py @@ -0,0 +1,100 @@ +import click +from flask.cli import AppGroup +from project import app, dump_path +from project.models import ( + Event, + EventPlace, + EventReference, + Location, + EventCategory, + EventOrganizer, + Image, + AdminUnit, +) +from sqlalchemy.orm import joinedload +import json +from project.api.event.schemas import EventDumpSchema +from project.api.place.schemas import PlaceDumpSchema +from project.api.location.schemas import LocationDumpSchema +from project.api.event_category.schemas import EventCategoryDumpSchema +from project.api.organizer.schemas import OrganizerDumpSchema +from project.api.image.schemas import ImageDumpSchema +from project.api.organization.schemas import OrganizationDumpSchema +from project.api.event_reference.schemas import EventReferenceDumpSchema +import os.path +import shutil +import pathlib + +dump_cli = AppGroup("dump") + + +def dump_items(items, schema, file_base_name, dump_path): + result = schema.dump(items) + path = os.path.join(dump_path, file_base_name + ".json") + + with open(path, "w") as outfile: + json.dump(result, outfile, ensure_ascii=False) + + click.echo(f"{len(items)} item(s) dumped to {path}.") + + +@dump_cli.command("all") +def dump_all(): + # Setup temp dir + tmp_path = os.path.join(dump_path, "tmp") + pathlib.Path(tmp_path).mkdir(parents=True, exist_ok=True) + + # Events + events = Event.query.options(joinedload(Event.categories)).all() + dump_items(events, EventDumpSchema(many=True), "events", tmp_path) + + # Places + places = EventPlace.query.all() + dump_items(places, PlaceDumpSchema(many=True), "places", tmp_path) + + # Locations + locations = Location.query.all() + dump_items(locations, LocationDumpSchema(many=True), "locations", tmp_path) + + # Event categories + event_categories = EventCategory.query.all() + dump_items( + event_categories, + EventCategoryDumpSchema(many=True), + "event_categories", + tmp_path, + ) + + # Organizers + organizers = EventOrganizer.query.all() + dump_items(organizers, OrganizerDumpSchema(many=True), "organizers", tmp_path) + + # Images + images = Image.query.all() + dump_items(images, ImageDumpSchema(many=True), "images", tmp_path) + + # Organizations + organizations = AdminUnit.query.all() + dump_items( + organizations, OrganizationDumpSchema(many=True), "organizations", tmp_path + ) + + # Event references + event_references = EventReference.query.all() + dump_items( + event_references, + EventReferenceDumpSchema(many=True), + "event_references", + tmp_path, + ) + + # Zip + zip_base_name = os.path.join(dump_path, "all") + zip_path = shutil.make_archive(zip_base_name, "zip", tmp_path) + click.echo(f"Zipped all up to {zip_path}.") + + # Clean up temp dir + shutil.rmtree(tmp_path, ignore_errors=True) + + +app.cli.add_command(dump_cli) diff --git a/project/services/event.py b/project/services/event.py index c4c10a9..692f5b2 100644 --- a/project/services/event.py +++ b/project/services/event.py @@ -1,10 +1,13 @@ from project import db from project.models import ( + AdminUnit, EventCategory, Event, EventDate, + EventOrganizer, EventReference, EventPlace, + Image, Location, ) from project.dateutils import ( @@ -14,6 +17,7 @@ from project.dateutils import ( ) from sqlalchemy import and_, or_, func from sqlalchemy.sql import extract +from sqlalchemy.orm import joinedload, contains_eager from dateutil.relativedelta import relativedelta @@ -89,9 +93,24 @@ def get_event_dates_query(params): date_filter = and_(date_filter, extract("dow", EventDate.start).in_(weekdays)) return ( - EventDate.query.join(Event) - .join(EventPlace, isouter=True) - .join(Location, isouter=True) + EventDate.query.join(EventDate.event) + .join(Event.event_place, isouter=True) + .join(EventPlace.location, isouter=True) + .options( + contains_eager(EventDate.event) + .contains_eager(Event.event_place) + .contains_eager(EventPlace.location), + joinedload(EventDate.event) + .joinedload(Event.categories) + .load_only(EventCategory.id, EventCategory.name), + joinedload(EventDate.event) + .joinedload(Event.organizer) + .load_only(EventOrganizer.id, EventOrganizer.name), + joinedload(EventDate.event).joinedload(Event.photo).load_only(Image.id), + joinedload(EventDate.event) + .joinedload(Event.admin_unit) + .load_only(AdminUnit.id, AdminUnit.name), + ) .filter(date_filter) .filter(event_filter) .order_by(EventDate.start) @@ -117,6 +136,13 @@ def get_events_query(params): return ( Event.query.join(EventPlace, isouter=True) .join(Location, isouter=True) + .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.start) ) diff --git a/project/services/reference.py b/project/services/reference.py index 99e2c30..846cff9 100644 --- a/project/services/reference.py +++ b/project/services/reference.py @@ -1,5 +1,6 @@ from project import db from project.models import ( + Event, EventReference, EventReferenceRequest, EventReferenceRequestReviewStatus, @@ -24,6 +25,14 @@ def create_event_reference_for_request(request): return result +def get_reference_incoming_query(admin_unit): + return EventReference.query.filter(EventReference.admin_unit_id == admin_unit.id) + + +def get_reference_outgoing_query(admin_unit): + return EventReference.query.join(Event).filter(Event.admin_unit_id == admin_unit.id) + + def get_reference_requests_incoming_query(admin_unit): return EventReferenceRequest.query.filter( and_( diff --git a/project/templates/developer/read.html b/project/templates/developer/read.html index 0bd63b5..f652518 100644 --- a/project/templates/developer/read.html +++ b/project/templates/developer/read.html @@ -7,18 +7,21 @@

Developer

-

Endpoint for all events

- -

Open

- -

Endpoint to search events

-{% set search_url = url_for('api_event_dates', page='1', per_page='10', coordinate='51.9077888,10.4333312', distance='1000', date_from='2020-10-03', date_to='2021-10-03', keyword='stadtrundgang', _external=True) %} - -

Open

- -

Documentation

+

API

+ +

Data download

+
    +
  • + {% if dump_file %} + Dump of all data {{ dump_file.ctime | datetimeformat }} {{ dump_file.size }} Bytes + {% else %} + No files available + {% endif %} +
  • +
  • The data file format is part of the API spec. Watch for the *Dump models.
diff --git a/project/templates/event_date/list.html b/project/templates/event_date/list.html index ea74e4d..43f5774 100644 --- a/project/templates/event_date/list.html +++ b/project/templates/event_date/list.html @@ -130,7 +130,7 @@ req_data += '&page=' + page + '&per_page=' + per_page; $.ajax({ - url: "{{ url_for('eventdatesearchresource') }}", + url: "{{ url_for('api_v1_event_date_search') }}", type: "get", dataType: "json", data: req_data, diff --git a/project/templates/planing/list.html b/project/templates/planing/list.html index d65f3cf..62eb1fd 100644 --- a/project/templates/planing/list.html +++ b/project/templates/planing/list.html @@ -19,7 +19,7 @@ $( function() { tbody.empty(); $.ajax({ - url: "{{ url_for('eventdatesearchresource', per_page=max_events) }}", + url: "{{ url_for('api_v1_event_date_search', per_page=max_events) }}", type: "get", dataType: "json", data: $(this).serialize(), diff --git a/project/views/dump.py b/project/views/dump.py new file mode 100644 index 0000000..b87bb47 --- /dev/null +++ b/project/views/dump.py @@ -0,0 +1,7 @@ +from project import app, dump_path +from flask import send_from_directory + + +@app.route("/dump/") +def dump_files(path): + return send_from_directory(dump_path, path) diff --git a/project/views/reference.py b/project/views/reference.py index 1606cb5..70dfbb8 100644 --- a/project/views/reference.py +++ b/project/views/reference.py @@ -9,6 +9,10 @@ from project.forms.reference import ( UpdateEventReferenceForm, DeleteReferenceForm, ) +from project.services.reference import ( + get_reference_incoming_query, + get_reference_outgoing_query, +) from flask import render_template, flash, redirect, url_for, abort from flask_babelex import gettext from flask_security import auth_required @@ -86,7 +90,7 @@ def event_reference_update(id): def manage_admin_unit_references_incoming(id): admin_unit = get_admin_unit_for_manage_or_404(id) references = ( - EventReference.query.filter(EventReference.admin_unit_id == admin_unit.id) + get_reference_incoming_query(admin_unit) .order_by(desc(EventReference.created_at)) .paginate() ) @@ -104,8 +108,7 @@ def manage_admin_unit_references_incoming(id): def manage_admin_unit_references_outgoing(id): admin_unit = get_admin_unit_for_manage_or_404(id) references = ( - EventReference.query.join(Event) - .filter(Event.admin_unit_id == admin_unit.id) + get_reference_outgoing_query(admin_unit) .order_by(desc(EventReference.created_at)) .paginate() ) diff --git a/project/views/root.py b/project/views/root.py index 7695dcf..3c55f66 100644 --- a/project/views/root.py +++ b/project/views/root.py @@ -1,9 +1,10 @@ -from project import app +from project import app, dump_path from project.services.admin import upsert_settings from project.views.utils import track_analytics from flask import url_for, render_template, request, redirect from flask_babelex import gettext from markupsafe import Markup +import os.path @app.route("/") @@ -54,4 +55,15 @@ def privacy(): @app.route("/developer") def developer(): - return render_template("developer/read.html") + file_name = "all.zip" + all_path = os.path.join(dump_path, file_name) + dump_file = None + + if os.path.exists(all_path): + dump_file = { + "url": url_for("dump_files", path=file_name), + "size": os.path.getsize(all_path), + "ctime": os.path.getctime(all_path), + } + + return render_template("developer/read.html", dump_file=dump_file) diff --git a/tests/api/test_dump.py b/tests/api/test_dump.py new file mode 100644 index 0000000..7578c62 --- /dev/null +++ b/tests/api/test_dump.py @@ -0,0 +1,3 @@ +def test_read(client, seeder, utils): + response = utils.get_endpoint("api_v1_dump") + utils.assert_response_notFound(response) diff --git a/tests/api/test_event.py b/tests/api/test_event.py index 5796f23..34b7499 100644 --- a/tests/api/test_event.py +++ b/tests/api/test_event.py @@ -12,7 +12,7 @@ def test_read(client, app, db, seeder, utils): update_event(event) db.session.commit() - url = utils.get_url("eventresource", id=event_id) + url = utils.get_url("api_v1_event", id=event_id) response = utils.get_ok(url) assert response.json["status"] == "scheduled" @@ -21,7 +21,7 @@ def test_list(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() seeder.create_event(admin_unit_id) - url = utils.get_url("eventlistresource") + url = utils.get_url("api_v1_event_list") utils.get_ok(url) @@ -29,7 +29,7 @@ def test_search(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() seeder.create_event(admin_unit_id) - url = utils.get_url("eventsearchresource") + url = utils.get_url("api_v1_event_search") utils.get_ok(url) @@ -37,5 +37,5 @@ def test_dates(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() event_id = seeder.create_event(admin_unit_id) - url = utils.get_url("eventdatesresource", id=event_id) + url = utils.get_url("api_v1_event_dates", id=event_id) utils.get_ok(url) diff --git a/tests/api/test_event_category.py b/tests/api/test_event_category.py index 7d489e5..4184b33 100644 --- a/tests/api/test_event_category.py +++ b/tests/api/test_event_category.py @@ -2,5 +2,5 @@ def test_list(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() seeder.create_event(admin_unit_id) - url = utils.get_url("eventcategorylistresource") + url = utils.get_url("api_v1_event_category_list") utils.get_ok(url) diff --git a/tests/api/test_event_date.py b/tests/api/test_event_date.py index 4354d42..60d408d 100644 --- a/tests/api/test_event_date.py +++ b/tests/api/test_event_date.py @@ -2,7 +2,7 @@ def test_read(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() seeder.create_event(admin_unit_id) - url = utils.get_url("eventdateresource", id=1) + url = utils.get_url("api_v1_event_date", id=1) utils.get_ok(url) @@ -10,7 +10,7 @@ def test_list(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() seeder.create_event(admin_unit_id) - url = utils.get_url("eventdatelistresource") + url = utils.get_url("api_v1_event_date_list") utils.get_ok(url) @@ -18,5 +18,5 @@ def test_search(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() seeder.create_event(admin_unit_id) - url = utils.get_url("eventdatesearchresource") + url = utils.get_url("api_v1_event_date_search") utils.get_ok(url) diff --git a/tests/api/test_event_reference.py b/tests/api/test_event_reference.py new file mode 100644 index 0000000..84ea0d0 --- /dev/null +++ b/tests/api/test_event_reference.py @@ -0,0 +1,11 @@ +def test_read(client, seeder, utils): + user_id, admin_unit_id = seeder.setup_base() + ( + other_user_id, + other_admin_unit_id, + event_id, + reference_id, + ) = seeder.create_any_reference(admin_unit_id) + + url = utils.get_url("api_v1_event_reference", id=reference_id) + utils.get_ok(url) diff --git a/tests/api/test_image.py b/tests/api/test_image.py index a4b5154..e33af36 100644 --- a/tests/api/test_image.py +++ b/tests/api/test_image.py @@ -2,5 +2,5 @@ def test_read(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() image_id = seeder.upsert_default_image() - url = utils.get_url("imageresource", id=image_id) + url = utils.get_url("api_v1_image", id=image_id) utils.get_ok(url) diff --git a/tests/api/test_location.py b/tests/api/test_location.py index dfb1994..241cc9b 100644 --- a/tests/api/test_location.py +++ b/tests/api/test_location.py @@ -15,6 +15,6 @@ def test_read(client, app, db, seeder, utils): db.session.commit() location_id = location.id - url = utils.get_url("locationresource", id=location_id) + url = utils.get_url("api_v1_location", id=location_id) response = utils.get_ok(url) assert response.json["latitude"] == "51.9077888000000000" diff --git a/tests/api/test_organization.py b/tests/api/test_organization.py index a7f35eb..55d34fc 100644 --- a/tests/api/test_organization.py +++ b/tests/api/test_organization.py @@ -1,21 +1,14 @@ def test_read(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() - url = utils.get_url("organizationresource", id=admin_unit_id) + url = utils.get_url("api_v1_organization", id=admin_unit_id) utils.get_ok(url) def test_list(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() - url = utils.get_url("organizationlistresource", keyword="crew") - utils.get_ok(url) - - -def test_read_by_short_name(client, seeder, utils): - user_id, admin_unit_id = seeder.setup_base() - - url = utils.get_url("organizationbyshortnameresource", short_name="meinecrew") + url = utils.get_url("api_v1_organization_list", keyword="crew") utils.get_ok(url) @@ -23,7 +16,7 @@ def test_event_date_search(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() seeder.create_event(admin_unit_id) - url = utils.get_url("organizationeventdatesearchresource", id=admin_unit_id) + url = utils.get_url("api_v1_organization_event_date_search", id=admin_unit_id) utils.get_ok(url) @@ -31,7 +24,7 @@ def test_event_search(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() seeder.create_event(admin_unit_id) - url = utils.get_url("organizationeventsearchresource", id=admin_unit_id) + url = utils.get_url("api_v1_organization_event_search", id=admin_unit_id) utils.get_ok(url) @@ -40,7 +33,7 @@ def test_organizers(client, seeder, utils): seeder.upsert_default_event_organizer(admin_unit_id) url = utils.get_url( - "organizationorganizerlistresource", id=admin_unit_id, name="crew" + "api_v1_organization_organizer_list", id=admin_unit_id, name="crew" ) utils.get_ok(url) @@ -49,5 +42,38 @@ def test_places(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() seeder.upsert_default_event_place(admin_unit_id) - url = utils.get_url("organizationplacelistresource", id=admin_unit_id, name="crew") + url = utils.get_url("api_v1_organization_place_list", id=admin_unit_id, name="crew") + utils.get_ok(url) + + +def test_references_incoming(client, seeder, utils): + user_id, admin_unit_id = seeder.setup_base() + ( + other_user_id, + other_admin_unit_id, + event_id, + reference_id, + ) = seeder.create_any_reference(admin_unit_id) + + url = utils.get_url( + "api_v1_organization_incoming_event_reference_list", + id=admin_unit_id, + name="crew", + ) + utils.get_ok(url) + + +def test_references_outgoing(client, seeder, utils): + user_id, admin_unit_id = seeder.setup_base() + event_id = seeder.create_event(admin_unit_id) + + other_user_id = seeder.create_user("other@test.de") + other_admin_unit_id = seeder.create_admin_unit(other_user_id, "Other Crew") + seeder.create_reference(event_id, other_admin_unit_id) + + url = utils.get_url( + "api_v1_organization_outgoing_event_reference_list", + id=admin_unit_id, + name="crew", + ) utils.get_ok(url) diff --git a/tests/api/test_organizer.py b/tests/api/test_organizer.py index e1cbc7b..34acd1c 100644 --- a/tests/api/test_organizer.py +++ b/tests/api/test_organizer.py @@ -2,5 +2,5 @@ def test_read(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() organizer_id = seeder.upsert_default_event_organizer(admin_unit_id) - url = utils.get_url("organizerresource", id=organizer_id) + url = utils.get_url("api_v1_organizer", id=organizer_id) utils.get_ok(url) diff --git a/tests/api/test_place.py b/tests/api/test_place.py index 9669a89..ce33bce 100644 --- a/tests/api/test_place.py +++ b/tests/api/test_place.py @@ -2,5 +2,5 @@ def test_read(client, app, db, seeder, utils): user_id, admin_unit_id = seeder.setup_base() place_id = seeder.upsert_default_event_place(admin_unit_id) - url = utils.get_url("placeresource", id=place_id) + url = utils.get_url("api_v1_place", id=place_id) utils.get_ok(url) diff --git a/tests/cli/test_dump.py b/tests/cli/test_dump.py new file mode 100644 index 0000000..6031365 --- /dev/null +++ b/tests/cli/test_dump.py @@ -0,0 +1,9 @@ +def test_all(client, seeder, app, utils): + user_id, admin_unit_id = seeder.setup_base() + seeder.create_event(admin_unit_id, "RRULE:FREQ=DAILY;COUNT=7") + + runner = app.test_cli_runner() + result = runner.invoke(args=["dump", "all"]) + assert "Zipped all up" in result.output + + utils.get_endpoint_ok("dump_files", path="all.zip")