Repository: fossasia/open-event-server

Issue:
API & Placeholder images
In our new API-Centric approach, how do we handle placeholder images (based on category/topic/sub-topic) ? 

1. Does the API response for an event itself returns a placeholder image URL in the place of the background image if the background image doesn't exist ?
2. Or, does the API response for an event return `null` for a non-existent image and we have a separate endpoint that provides all the placeholder images for each category and the frontend can use both to display the correct placeholder.

@mariobehling @aviaryan @SaptakS @shubham-padia @magdalenesuo your views ?
API & Placeholder images
In our new API-Centric approach, how do we handle placeholder images (based on category/topic/sub-topic) ? 

1. Does the API response for an event itself returns a placeholder image URL in the place of the background image if the background image doesn't exist ?
2. Or, does the API response for an event return `null` for a non-existent image and we have a separate endpoint that provides all the placeholder images for each category and the frontend can use both to display the correct placeholder.

@mariobehling @aviaryan @SaptakS @shubham-padia @magdalenesuo your views ?


Discussion:
Right now, in the /browse UI, placeholder images are not being differentiated from a real image at all. So I think we should have the API response return placeholder image if event's background is null.
But, after we move to the API centric approach, the edit event page may also use the same API for fetching existing event details. It won't be good to return placeholder images there. 
If the endpoints for fetching `/browse` event information and fetching edit event's event information are different, we can easily do things in the backend to return placeholder images only where they are really needed. 

In short, I am with returning placeholder images in the same API but this depends on how API endpoints are going to be defined. 
I think fetching from a separate endpoint is a better option. That way things would be clean and separate and won't create confusion in terms of API results returned. If there is no background image, the API endpoint should return a null value instead of a placeholder value to avoid confusion at later stages of production. This also helps in avoiding any kind of interference with editing or updating event page. We can put the logic in the front-end only. I don't think it will make the js much heavy because it should be done by a single if check.

Another alternative which I think is even better is in the API endpoint for events, we add an attribute `placeholder_image` which will contain the placeholder image depending on the category even if a background image is uploaded. Then in front-end we will see that if background image present, use that, else use the placeholder_image attribute. This way the number of API requests will also stay minimal and there won't be any problem in readability as well. What do you think @niranjan94 @aviaryan @shubham-padia @magdalenesuo ?

> Another alternative which I think is even better is in the API endpoint for events, we add an attribute placeholder_image which will contain the placeholder image depending on the category even if a background image is uploaded.

:+1: Since an event will only have one placeholder image, adding a field like this won't cause any overhead. And this certainly removes all ambiguities. 
> Another alternative which I think is even better is in the API endpoint for events, we add an attribute placeholder_image which will contain the placeholder image depending on the category even if a background image is uploaded.

@SaptakS I like the option suggested by you ... 👍 
So should I work on this? @niranjan94 
@aviaryan yes. You can proceed. 
Right now, in the /browse UI, placeholder images are not being differentiated from a real image at all. So I think we should have the API response return placeholder image if event's background is null.
But, after we move to the API centric approach, the edit event page may also use the same API for fetching existing event details. It won't be good to return placeholder images there. 
If the endpoints for fetching `/browse` event information and fetching edit event's event information are different, we can easily do things in the backend to return placeholder images only where they are really needed. 

In short, I am with returning placeholder images in the same API but this depends on how API endpoints are going to be defined. 
I think fetching from a separate endpoint is a better option. That way things would be clean and separate and won't create confusion in terms of API results returned. If there is no background image, the API endpoint should return a null value instead of a placeholder value to avoid confusion at later stages of production. This also helps in avoiding any kind of interference with editing or updating event page. We can put the logic in the front-end only. I don't think it will make the js much heavy because it should be done by a single if check.

Another alternative which I think is even better is in the API endpoint for events, we add an attribute `placeholder_image` which will contain the placeholder image depending on the category even if a background image is uploaded. Then in front-end we will see that if background image present, use that, else use the placeholder_image attribute. This way the number of API requests will also stay minimal and there won't be any problem in readability as well. What do you think @niranjan94 @aviaryan @shubham-padia @magdalenesuo ?

> Another alternative which I think is even better is in the API endpoint for events, we add an attribute placeholder_image which will contain the placeholder image depending on the category even if a background image is uploaded.

:+1: Since an event will only have one placeholder image, adding a field like this won't cause any overhead. And this certainly removes all ambiguities. 
> Another alternative which I think is even better is in the API endpoint for events, we add an attribute placeholder_image which will contain the placeholder image depending on the category even if a background image is uploaded.

@SaptakS I like the option suggested by you ... 👍 
So should I work on this? @niranjan94 
@aviaryan yes. You can proceed. 

Resolving patch:
diff --git a/app/api/events.py b/app/api/events.py
--- a/app/api/events.py
+++ b/app/api/events.py
@@ -23,7 +23,6 @@
 from app.api.helpers.utils import PAGINATED_MODEL, PaginatedResourceBase, \
     PAGE_PARAMS, POST_RESPONSES, PUT_RESPONSES, BaseDAO, ServiceDAO
 from app.api.helpers.utils import Resource, ETAG_HEADER_DEFN
-from app.api.helpers.custom_fields import Licence
 
 api = Namespace('events', description='Events')
 
@@ -76,6 +75,7 @@
     'latitude': fields.Float(),
     'longitude': fields.Float(),
     'background_image': fields.Upload(attribute='background_url'),
+    'placeholder_url': fields.PlaceHolder(attribute=lambda event: event),
     'description': fields.String(),
     'location_name': fields.String(),
     'searchable_location_name': fields.String(),
@@ -88,7 +88,7 @@
     'privacy': EventPrivacyField(),
     'ticket_url': fields.Uri(),
     'copyright': fields.Nested(EVENT_COPYRIGHT, allow_null=True),
-    'licence_details': Licence(attribute='copyright.licence', allow_null=True),
+    'licence_details': fields.Licence(attribute='copyright.licence', allow_null=True),
     'schedule_published_on': fields.DateTime(),
     'code_of_conduct': fields.String(),
     'social_links': fields.List(fields.Nested(SOCIAL_LINK), attribute='social_link'),
@@ -118,6 +118,7 @@
 del EVENT_POST['social_links']
 del EVENT_POST['version']
 del EVENT_POST['licence_details']
+del EVENT_POST['placeholder_url']
 
 # ###################
 # Data Access Objects
diff --git a/app/api/helpers/custom_fields.py b/app/api/helpers/custom_fields.py
--- a/app/api/helpers/custom_fields.py
+++ b/app/api/helpers/custom_fields.py
@@ -263,3 +263,13 @@ class Licence(Raw):
 
     def format(self, value):
         return DataGetter.get_licence_details(value)
+
+
+class PlaceHolder(CustomField):
+    """
+    Background Image PlaceHolder field for Events
+    """
+    __schema_example__ = 'http://url.com/img.ext'
+
+    def format(self, value):
+        return DataGetter.get_placeholder_url_by_event(value)
diff --git a/app/helpers/data_getter.py b/app/helpers/data_getter.py
--- a/app/helpers/data_getter.py
+++ b/app/helpers/data_getter.py
@@ -6,7 +6,7 @@
 import humanize
 import pytz
 import requests
-from flask import flash, abort
+from flask import flash, abort, request
 from flask import url_for
 from flask.ext import login
 from sqlalchemy import desc, asc, or_
@@ -591,6 +591,17 @@ def get_event_subtopics():
     def get_event_default_images():
         return DEFAULT_EVENT_IMAGES
 
+    @staticmethod
+    def get_placeholder_url_by_event(event):
+        custom_placeholder = DataGetter.get_custom_placeholder_by_name(event.sub_topic or event.topic or 'Other')
+        if custom_placeholder:
+            return custom_placeholder.url
+        else:
+            default_images = DataGetter.get_event_default_images()
+            for key in [event.sub_topic, event.topic, 'Other']:
+                if key in default_images:
+                    return request.url_root.strip('/') + url_for('static', filename='placeholders/' + default_images[key])
+
     @staticmethod
     def get_all_mails(count=300):
         """


Test patch:
diff --git a/tests/unittests/api/test_get_api_200.py b/tests/unittests/api/test_get_api_200.py
--- a/tests/unittests/api/test_get_api_200.py
+++ b/tests/unittests/api/test_get_api_200.py
@@ -33,7 +33,7 @@ def _test_path(self, path, *strings):
 
     def test_event_api(self):
         path = get_path(1)
-        self._test_path(path, 'TestEvent')
+        self._test_path(path, 'TestEvent', 'placeholder')
 
     def test_track_api(self):
         path = get_path(1, 'tracks', 1)
diff --git a/tests/unittests/api/test_put_api.py b/tests/unittests/api/test_put_api.py
--- a/tests/unittests/api/test_put_api.py
+++ b/tests/unittests/api/test_put_api.py
@@ -119,7 +119,7 @@ def _test_change_json(self, old, new, field):
                     return i
         return False
 
-    def _test_model(self, name, data, path=None, exclude=None):
+    def _test_model(self, name, data, path=None, exclude=None, linked_pairs=None):
         if exclude is None:
             exclude = []
         if not path:
@@ -139,11 +139,11 @@ def _test_model(self, name, data, path=None, exclude=None):
                              msg='Key: %s\nMsg: %s' % (i, resp.data))
             # check persistence
             status = self._test_change_json(resp_old.data, resp.data, i)
-            if status:
+            if status and (linked_pairs.get(i) != status):
                 self.assertTrue(0, msg='Key %s changed in %s' % (status, i))
 
     def test_event_api(self):
-        self._test_model('event', POST_EVENT_DATA, exclude=['sub_topic'])
+        self._test_model('event', POST_EVENT_DATA, exclude=['sub_topic'], linked_pairs={'topic': 'placeholder_url'})
 
     def test_track_api(self):
         self._test_model('track', POST_TRACK_DATA)