Repository: oppia/oppia

Issue:
Return profile pictures together with the other data in the exploration summary tiles
The exploration summary data information that is returned in core.domain.summary_services should include dataURIs of the profile pictures of the contributors, probably obtained using a get_multi() (so that only one call is made to the datastore). Otherwise, many separate calls are made to the server in order to retrieve the profile images that are shown at the top right of the exploration tiles displayed via components/exploration_summary_tile_directive.html.

It probably makes sense to change the format of the return dict of the function in summary_services accordingly, as follows: replace the 'contributor_names' key with a 'contributors' key, whose value is a list of dicts; each dict in this list has two keys: 'name' and 'profile_picture_data_uri'. The change should be propagated up the stack as needed.



Discussion:
(@maitbayev, just thought I'd sanity-check, since you did a bunch of the contributor icon stuff: does this sound like a good idea to you?)

Hi @seanlip,
This is an amazing idea, I was going to suggest this.
It is slightly visible in slow internet. 

OK, great! I'll also keep it in mind for the default profile picture stuff (#1544).

In the meantime, would you be interested in taking on this issue?

Hi @seanlip,
Yes, sure. 

Thanks! I've assigned you :-)

@seanlip @maitbayev If you don't mind I can fix this.

Hi @satwik97,
Sure,  I am okay with that.
Thanks.

Thanks @maitbayev , a quick question , To test my implementaion I need contributors data associated to the explorations but even after loading the explorations , none of the explorations have contributors associated with them . 

Hi @satwik97,
That is right, they are uploaded by system users, which are ignored. 
Maybe you can create several users and explorations using your browser in your local running instance (i.e http://localhost:8181/gallery) ?
Let me know, if this works for you.
Thanks. 

@maitbayev Yeah that would work. But is there any way I can make the explorations data persistent so that I don't have to create explorations after every time I run the server.

Hi @satwik97,
I am not sure, whether there is an existing solution for this. Let's wait for the confirmation from @seanlip,

But, you can run the server once, after each change you don't have to restart, it will pickup and build your changes automatically. 
So, start the server, create necessary users and explorations, make changes to your code as long as you need. After each change your created data will stay.

Other solution is to create some script dedicated to your workflow. 

Hi @satwik97 -- you can use the `--save_datastore` flag when starting the server to preserve the datastore contents from the last run. See scripts/start.sh.

Thanks for taking on this issue!

@seanlip,
wow, awesome, I didn't know that.
Maybe we can mention it somewhere in the wiki ?

@maitbayev -- good idea! I added it to the various "Installing Oppia" pages.

Note: this is being worked on as part of #1589.

(@satwik97 -- I deassigned you since you're still working on #1572, hope that's OK.)


Resolving patch:
diff --git a/core/domain/summary_services.py b/core/domain/summary_services.py
--- a/core/domain/summary_services.py
+++ b/core/domain/summary_services.py
@@ -27,8 +27,14 @@ def get_human_readable_contributors_summary(contributors_summary):
     contributor_ids = contributors_summary.keys()
     contributor_usernames = user_services.get_human_readable_user_ids(
         contributor_ids)
+    contributor_profile_pictures = (
+        user_services.get_profile_pictures_by_user_ids(contributor_ids))
     return {
-        contributor_usernames[ind]: contributors_summary[contributor_ids[ind]]
+        contributor_usernames[ind]: {
+            'num_commits': contributors_summary[contributor_ids[ind]],
+            'profile_picture_data_url': contributor_profile_pictures[
+                contributor_ids[ind]]
+        }
         for ind in xrange(len(contributor_ids))
     }
 
diff --git a/core/domain/user_services.py b/core/domain/user_services.py
--- a/core/domain/user_services.py
+++ b/core/domain/user_services.py
@@ -201,6 +201,23 @@ def get_users_settings(user_ids):
     return result
 
 
+def get_profile_pictures_by_user_ids(user_ids):
+    """Gets the profile_picture_data_url from the domain objects
+    representing the settings for the given user_ids.
+
+    If the given user_id does not exist, the corresponding entry in the
+    returned list is None.
+    """
+    user_settings_models = user_models.UserSettingsModel.get_multi(user_ids)
+    result = {}
+    for model in user_settings_models:
+        if model:
+            result[model.id] = model.profile_picture_data_url
+        else:
+            result[model.id] = None
+    return result
+
+
 def get_user_settings(user_id, strict=False):
     """Return the user settings for a single user."""
     user_settings = get_users_settings([user_id])[0]


Test patch:
diff --git a/core/domain/summary_services_test.py b/core/domain/summary_services_test.py
--- a/core/domain/summary_services_test.py
+++ b/core/domain/summary_services_test.py
@@ -18,6 +18,7 @@
 from core.domain import exp_services_test
 from core.domain import rights_manager
 from core.domain import summary_services
+from core.domain import user_services
 import feconf
 
 
@@ -30,9 +31,17 @@ class ExplorationDisplayableSummaries(
     ALBERT_NAME = 'albert'
     BOB_NAME = 'bob'
 
+    USER_C_NAME = 'c'
+    USER_D_NAME = 'd'
+    USER_C_EMAIL = 'c@example.com'
+    USER_D_EMAIL = 'd@example.com'
+
+    USER_C_PROFILE_PICTURE = 'c_profile_picture'
+
     EXP_ID_1 = 'eid1'
     EXP_ID_2 = 'eid2'
     EXP_ID_3 = 'eid3'
+    EXP_ID_4 = 'eid4'
 
     EXPECTED_VERSION_1 = 4
     EXPECTED_VERSION_2 = 2
@@ -52,7 +61,12 @@ def setUp(self):
         - (8) Albert creates EXP_ID_3
         - (9) Albert publishes EXP_ID_3
         - (10) Albert deletes EXP_ID_3
+
+        - (1) User_3 (has a profile_picture) creates EXP_ID_4.
+        - (2) User_4 edits the title of EXP_ID_4.
+        - (3) User_4 edits the title of EXP_ID_4.
         """
+
         super(ExplorationDisplayableSummaries, self).setUp()
 
         self.albert_id = self.get_user_id_from_email(self.ALBERT_EMAIL)
@@ -98,10 +112,52 @@ def setUp(self):
         rights_manager.publish_exploration(self.albert_id, self.EXP_ID_3)
         exp_services.delete_exploration(self.albert_id, self.EXP_ID_3)
 
+        self.user_c_id = self.get_user_id_from_email(self.USER_C_EMAIL)
+        self.user_d_id = self.get_user_id_from_email(self.USER_D_EMAIL)
+        self.signup(self.USER_C_EMAIL, self.USER_C_NAME)
+        self.signup(self.USER_D_EMAIL, self.USER_D_NAME)
+        user_services.update_profile_picture_data_url(
+            self.user_c_id, self.USER_C_PROFILE_PICTURE)
+
+        self.save_new_valid_exploration(self.EXP_ID_4, self.user_c_id)
+        exp_services.update_exploration(
+            self.user_d_id, self.EXP_ID_4, [{
+                'cmd': 'edit_exploration_property',
+                'property_name': 'title',
+                'new_value': 'Exploration updated title'
+            }], 'Changed title once.')
+
+        exp_services.update_exploration(
+            self.user_d_id, self.EXP_ID_4, [{
+                'cmd': 'edit_exploration_property',
+                'property_name': 'title',
+                'new_value': 'Exploration updated title again'
+            }], 'Changed title twice.')
+
     def test_get_human_readable_contributors_summary(self):
         contributors_summary = {self.albert_id: 10, self.bob_id: 13}
         self.assertEqual({
-            self.ALBERT_NAME: 10, self.BOB_NAME: 13
+            self.ALBERT_NAME: {
+                'num_commits': 10,
+                'profile_picture_data_url': None
+            },
+            self.BOB_NAME: {
+                'num_commits': 13,
+                'profile_picture_data_url': None
+            }
+        }, summary_services.get_human_readable_contributors_summary(
+            contributors_summary))
+
+        contributors_summary = {self.user_c_id: 1, self.user_d_id: 2}
+        self.assertEqual({
+            self.USER_C_NAME: {
+                'num_commits': 1,
+                'profile_picture_data_url': self.USER_C_PROFILE_PICTURE
+            },
+            self.USER_D_NAME: {
+                'num_commits': 2,
+                'profile_picture_data_url': None
+            }
         }, summary_services.get_human_readable_contributors_summary(
             contributors_summary))
 
@@ -122,7 +178,12 @@ def test_get_displayable_exp_summary_dicts_matching_ids(self):
             'tags': [],
             'thumbnail_icon_url': '/images/gallery/thumbnails/Lightbulb.svg',
             'language_code': feconf.DEFAULT_LANGUAGE_CODE,
-            'human_readable_contributors_summary': {self.ALBERT_NAME: 2},
+            'human_readable_contributors_summary': {
+                self.ALBERT_NAME: {
+                    'num_commits': 2,
+                    'profile_picture_data_url': None
+                }
+            },
             'id': self.EXP_ID_2,
             'category': u'A category',
             'ratings': feconf.get_empty_ratings(),