We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
import footer styling regression


### Observed behavior

![image](https://user-images.githubusercontent.com/2367265/85073073-fef1cd80-b16e-11ea-98c5-8e2342430929.png)

### Expected behavior

button and text should be vertically centered, or the footer should be shorter in height



### Steps to reproduce

import

### Context


0.14 beta 3

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `kolibri/core/content/utils/channels.py`
Content:
```
1 import fnmatch
2 import logging
3 import os
4 
5 from django.core.cache import cache
6 from sqlalchemy.exc import DatabaseError
7 
8 from .paths import get_content_database_dir_path
9 from .sqlalchemybridge import Bridge
10 from kolibri.core.discovery.utils.filesystem import enumerate_mounted_disk_partitions
11 from kolibri.utils.uuids import is_valid_uuid
12 
13 logger = logging.getLogger(__name__)
14 
15 
16 def get_channel_ids_for_content_dirs(content_dirs):
17     database_dir_paths = [
18         get_content_database_dir_path(contentfolder=path) for path in content_dirs
19     ]
20     channel_ids = set()
21     for path in database_dir_paths:
22         channel_ids.update(get_channel_ids_for_content_database_dir(path))
23     return list(channel_ids)
24 
25 
26 def get_channel_ids_for_content_database_dir(content_database_dir):
27     """
28     Returns a list of channel IDs for the channel databases that exist in a content database directory.
29     """
30 
31     # immediately return an empty list if the content database directory doesn't exist
32     if not os.path.isdir(content_database_dir):
33         return []
34 
35     # get a list of all the database files in the directory, and extract IDs
36     db_list = fnmatch.filter(os.listdir(content_database_dir), "*.sqlite3")
37     db_names = [db.split(".sqlite3", 1)[0] for db in db_list]
38 
39     # determine which database names are valid, and only use those ones
40     valid_db_names = [name for name in db_names if is_valid_uuid(name)]
41     invalid_db_names = set(db_names) - set(valid_db_names)
42     if invalid_db_names:
43         logger.warning(
44             "Ignoring databases in content database directory '{directory}' with invalid names: {names}".format(
45                 directory=content_database_dir, names=invalid_db_names
46             )
47         )
48 
49     # nonexistent database files are created if we delete the files that have broken symbolic links;
50     # empty database files are created if we delete a database file while the server is running and connected to it;
51     # here, we delete and exclude such databases to avoid errors when we try to connect to them
52     db_files_to_remove = set({})
53     for db_name in valid_db_names:
54         filename = os.path.join(content_database_dir, "{}.sqlite3".format(db_name))
55         if not os.path.exists(filename) or os.path.getsize(filename) == 0:
56             db_files_to_remove.add(db_name)
57             os.remove(filename)
58 
59     if db_files_to_remove:
60         err_msg = (
61             "Removing nonexistent or empty databases in content database directory "
62             "'{directory}' with IDs: {names}.\nPlease import the channels again."
63         )
64         logger.warning(
65             err_msg.format(directory=content_database_dir, names=db_files_to_remove)
66         )
67     valid_dbs = list(set(valid_db_names) - set(db_files_to_remove))
68 
69     return valid_dbs
70 
71 
72 def enumerate_content_database_file_paths(content_database_dir):
73     full_dir_template = os.path.join(content_database_dir, "{}.sqlite3")
74     channel_ids = get_channel_ids_for_content_database_dir(content_database_dir)
75     return [full_dir_template.format(f) for f in channel_ids]
76 
77 
78 def read_channel_metadata_from_db_file(channeldbpath):
79     # import here to avoid circular imports whenever kolibri.core.content.models imports utils too
80     from kolibri.core.content.models import ChannelMetadata
81 
82     source = Bridge(sqlite_file_path=channeldbpath)
83 
84     ChannelMetadataClass = source.get_class(ChannelMetadata)
85 
86     source_channel_metadata = source.session.query(ChannelMetadataClass).all()[0]
87 
88     # Use the inferred version from the SQLAlchemy Bridge object, and set it as additional
89     # metadata on the channel data
90 
91     source_channel_metadata.inferred_schema_version = source.schema_version
92 
93     source.end()
94 
95     # Adds an attribute `root_id` when `root_id` does not exist to match with
96     # the latest schema.
97     if not hasattr(source_channel_metadata, "root_id"):
98         setattr(
99             source_channel_metadata,
100             "root_id",
101             getattr(source_channel_metadata, "root_pk"),
102         )
103 
104     return source_channel_metadata
105 
106 
107 def get_channels_for_data_folder(datafolder):
108     channels = []
109     for path in enumerate_content_database_file_paths(
110         get_content_database_dir_path(datafolder)
111     ):
112         try:
113             channel = read_channel_metadata_from_db_file(path)
114         except DatabaseError:
115             logger.warning(
116                 "Tried to import channel from database file {}, but the file was corrupted.".format(
117                     path
118                 )
119             )
120             continue
121         channel_data = {
122             "path": path,
123             "id": channel.id,
124             "name": channel.name,
125             "description": channel.description,
126             "tagline": channel.tagline,
127             "thumbnail": channel.thumbnail,
128             "version": channel.version,
129             "root": channel.root_id,
130             "author": channel.author,
131             "last_updated": getattr(channel, "last_updated", None),
132             "lang_code": getattr(channel, "lang_code", None),
133             "lang_name": getattr(channel, "lang_name", None),
134         }
135         channels.append(channel_data)
136     return channels
137 
138 
139 # Use this to cache mounted drive information when
140 # it has already been fetched for querying by drive id
141 MOUNTED_DRIVES_CACHE_KEY = "mounted_drives_cache_key"
142 
143 
144 def get_mounted_drives_with_channel_info():
145     drives = enumerate_mounted_disk_partitions()
146     for drive in drives.values():
147         drive.metadata["channels"] = (
148             get_channels_for_data_folder(drive.datafolder) if drive.datafolder else []
149         )
150     cache.set(MOUNTED_DRIVES_CACHE_KEY, drives, 3600)
151     return drives
152 
153 
154 def get_mounted_drive_by_id(drive_id):
155     drives = cache.get(MOUNTED_DRIVES_CACHE_KEY)
156     if drives is None or drives.get(drive_id, None) is None:
157         drives = get_mounted_drives_with_channel_info()
158     return drives[drive_id]
159
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/kolibri/core/content/utils/channels.py b/kolibri/core/content/utils/channels.py
--- a/kolibri/core/content/utils/channels.py
+++ b/kolibri/core/content/utils/channels.py
@@ -123,7 +123,7 @@
             "id": channel.id,
             "name": channel.name,
             "description": channel.description,
-            "tagline": channel.tagline,
+            "tagline": getattr(channel, "tagline", ""),
             "thumbnail": channel.thumbnail,
             "version": channel.version,
             "root": channel.root_id,

{"golden_diff": "diff --git a/kolibri/core/content/utils/channels.py b/kolibri/core/content/utils/channels.py\n--- a/kolibri/core/content/utils/channels.py\n+++ b/kolibri/core/content/utils/channels.py\n@@ -123,7 +123,7 @@\n             \"id\": channel.id,\n             \"name\": channel.name,\n             \"description\": channel.description,\n-            \"tagline\": channel.tagline,\n+            \"tagline\": getattr(channel, \"tagline\", \"\"),\n             \"thumbnail\": channel.thumbnail,\n             \"version\": channel.version,\n             \"root\": channel.root_id,\n", "issue": "import footer styling regression\n\r\n\r\n### Observed behavior\r\n\r\n![image](https://user-images.githubusercontent.com/2367265/85073073-fef1cd80-b16e-11ea-98c5-8e2342430929.png)\r\n\r\n### Expected behavior\r\n\r\nbutton and text should be vertically centered, or the footer should be shorter in height\r\n\r\n\r\n\r\n### Steps to reproduce\r\n\r\nimport\r\n\r\n### Context\r\n\r\n\r\n0.14 beta 3\n", "before_files": [{"content": "import fnmatch\nimport logging\nimport os\n\nfrom django.core.cache import cache\nfrom sqlalchemy.exc import DatabaseError\n\nfrom .paths import get_content_database_dir_path\nfrom .sqlalchemybridge import Bridge\nfrom kolibri.core.discovery.utils.filesystem import enumerate_mounted_disk_partitions\nfrom kolibri.utils.uuids import is_valid_uuid\n\nlogger = logging.getLogger(__name__)\n\n\ndef get_channel_ids_for_content_dirs(content_dirs):\n    database_dir_paths = [\n        get_content_database_dir_path(contentfolder=path) for path in content_dirs\n    ]\n    channel_ids = set()\n    for path in database_dir_paths:\n        channel_ids.update(get_channel_ids_for_content_database_dir(path))\n    return list(channel_ids)\n\n\ndef get_channel_ids_for_content_database_dir(content_database_dir):\n    \"\"\"\n    Returns a list of channel IDs for the channel databases that exist in a content database directory.\n    \"\"\"\n\n    # immediately return an empty list if the content database directory doesn't exist\n    if not os.path.isdir(content_database_dir):\n        return []\n\n    # get a list of all the database files in the directory, and extract IDs\n    db_list = fnmatch.filter(os.listdir(content_database_dir), \"*.sqlite3\")\n    db_names = [db.split(\".sqlite3\", 1)[0] for db in db_list]\n\n    # determine which database names are valid, and only use those ones\n    valid_db_names = [name for name in db_names if is_valid_uuid(name)]\n    invalid_db_names = set(db_names) - set(valid_db_names)\n    if invalid_db_names:\n        logger.warning(\n            \"Ignoring databases in content database directory '{directory}' with invalid names: {names}\".format(\n                directory=content_database_dir, names=invalid_db_names\n            )\n        )\n\n    # nonexistent database files are created if we delete the files that have broken symbolic links;\n    # empty database files are created if we delete a database file while the server is running and connected to it;\n    # here, we delete and exclude such databases to avoid errors when we try to connect to them\n    db_files_to_remove = set({})\n    for db_name in valid_db_names:\n        filename = os.path.join(content_database_dir, \"{}.sqlite3\".format(db_name))\n        if not os.path.exists(filename) or os.path.getsize(filename) == 0:\n            db_files_to_remove.add(db_name)\n            os.remove(filename)\n\n    if db_files_to_remove:\n        err_msg = (\n            \"Removing nonexistent or empty databases in content database directory \"\n            \"'{directory}' with IDs: {names}.\\nPlease import the channels again.\"\n        )\n        logger.warning(\n            err_msg.format(directory=content_database_dir, names=db_files_to_remove)\n        )\n    valid_dbs = list(set(valid_db_names) - set(db_files_to_remove))\n\n    return valid_dbs\n\n\ndef enumerate_content_database_file_paths(content_database_dir):\n    full_dir_template = os.path.join(content_database_dir, \"{}.sqlite3\")\n    channel_ids = get_channel_ids_for_content_database_dir(content_database_dir)\n    return [full_dir_template.format(f) for f in channel_ids]\n\n\ndef read_channel_metadata_from_db_file(channeldbpath):\n    # import here to avoid circular imports whenever kolibri.core.content.models imports utils too\n    from kolibri.core.content.models import ChannelMetadata\n\n    source = Bridge(sqlite_file_path=channeldbpath)\n\n    ChannelMetadataClass = source.get_class(ChannelMetadata)\n\n    source_channel_metadata = source.session.query(ChannelMetadataClass).all()[0]\n\n    # Use the inferred version from the SQLAlchemy Bridge object, and set it as additional\n    # metadata on the channel data\n\n    source_channel_metadata.inferred_schema_version = source.schema_version\n\n    source.end()\n\n    # Adds an attribute `root_id` when `root_id` does not exist to match with\n    # the latest schema.\n    if not hasattr(source_channel_metadata, \"root_id\"):\n        setattr(\n            source_channel_metadata,\n            \"root_id\",\n            getattr(source_channel_metadata, \"root_pk\"),\n        )\n\n    return source_channel_metadata\n\n\ndef get_channels_for_data_folder(datafolder):\n    channels = []\n    for path in enumerate_content_database_file_paths(\n        get_content_database_dir_path(datafolder)\n    ):\n        try:\n            channel = read_channel_metadata_from_db_file(path)\n        except DatabaseError:\n            logger.warning(\n                \"Tried to import channel from database file {}, but the file was corrupted.\".format(\n                    path\n                )\n            )\n            continue\n        channel_data = {\n            \"path\": path,\n            \"id\": channel.id,\n            \"name\": channel.name,\n            \"description\": channel.description,\n            \"tagline\": channel.tagline,\n            \"thumbnail\": channel.thumbnail,\n            \"version\": channel.version,\n            \"root\": channel.root_id,\n            \"author\": channel.author,\n            \"last_updated\": getattr(channel, \"last_updated\", None),\n            \"lang_code\": getattr(channel, \"lang_code\", None),\n            \"lang_name\": getattr(channel, \"lang_name\", None),\n        }\n        channels.append(channel_data)\n    return channels\n\n\n# Use this to cache mounted drive information when\n# it has already been fetched for querying by drive id\nMOUNTED_DRIVES_CACHE_KEY = \"mounted_drives_cache_key\"\n\n\ndef get_mounted_drives_with_channel_info():\n    drives = enumerate_mounted_disk_partitions()\n    for drive in drives.values():\n        drive.metadata[\"channels\"] = (\n            get_channels_for_data_folder(drive.datafolder) if drive.datafolder else []\n        )\n    cache.set(MOUNTED_DRIVES_CACHE_KEY, drives, 3600)\n    return drives\n\n\ndef get_mounted_drive_by_id(drive_id):\n    drives = cache.get(MOUNTED_DRIVES_CACHE_KEY)\n    if drives is None or drives.get(drive_id, None) is None:\n        drives = get_mounted_drives_with_channel_info()\n    return drives[drive_id]\n", "path": "kolibri/core/content/utils/channels.py"}], "after_files": [{"content": "import fnmatch\nimport logging\nimport os\n\nfrom django.core.cache import cache\nfrom sqlalchemy.exc import DatabaseError\n\nfrom .paths import get_content_database_dir_path\nfrom .sqlalchemybridge import Bridge\nfrom kolibri.core.discovery.utils.filesystem import enumerate_mounted_disk_partitions\nfrom kolibri.utils.uuids import is_valid_uuid\n\nlogger = logging.getLogger(__name__)\n\n\ndef get_channel_ids_for_content_dirs(content_dirs):\n    database_dir_paths = [\n        get_content_database_dir_path(contentfolder=path) for path in content_dirs\n    ]\n    channel_ids = set()\n    for path in database_dir_paths:\n        channel_ids.update(get_channel_ids_for_content_database_dir(path))\n    return list(channel_ids)\n\n\ndef get_channel_ids_for_content_database_dir(content_database_dir):\n    \"\"\"\n    Returns a list of channel IDs for the channel databases that exist in a content database directory.\n    \"\"\"\n\n    # immediately return an empty list if the content database directory doesn't exist\n    if not os.path.isdir(content_database_dir):\n        return []\n\n    # get a list of all the database files in the directory, and extract IDs\n    db_list = fnmatch.filter(os.listdir(content_database_dir), \"*.sqlite3\")\n    db_names = [db.split(\".sqlite3\", 1)[0] for db in db_list]\n\n    # determine which database names are valid, and only use those ones\n    valid_db_names = [name for name in db_names if is_valid_uuid(name)]\n    invalid_db_names = set(db_names) - set(valid_db_names)\n    if invalid_db_names:\n        logger.warning(\n            \"Ignoring databases in content database directory '{directory}' with invalid names: {names}\".format(\n                directory=content_database_dir, names=invalid_db_names\n            )\n        )\n\n    # nonexistent database files are created if we delete the files that have broken symbolic links;\n    # empty database files are created if we delete a database file while the server is running and connected to it;\n    # here, we delete and exclude such databases to avoid errors when we try to connect to them\n    db_files_to_remove = set({})\n    for db_name in valid_db_names:\n        filename = os.path.join(content_database_dir, \"{}.sqlite3\".format(db_name))\n        if not os.path.exists(filename) or os.path.getsize(filename) == 0:\n            db_files_to_remove.add(db_name)\n            os.remove(filename)\n\n    if db_files_to_remove:\n        err_msg = (\n            \"Removing nonexistent or empty databases in content database directory \"\n            \"'{directory}' with IDs: {names}.\\nPlease import the channels again.\"\n        )\n        logger.warning(\n            err_msg.format(directory=content_database_dir, names=db_files_to_remove)\n        )\n    valid_dbs = list(set(valid_db_names) - set(db_files_to_remove))\n\n    return valid_dbs\n\n\ndef enumerate_content_database_file_paths(content_database_dir):\n    full_dir_template = os.path.join(content_database_dir, \"{}.sqlite3\")\n    channel_ids = get_channel_ids_for_content_database_dir(content_database_dir)\n    return [full_dir_template.format(f) for f in channel_ids]\n\n\ndef read_channel_metadata_from_db_file(channeldbpath):\n    # import here to avoid circular imports whenever kolibri.core.content.models imports utils too\n    from kolibri.core.content.models import ChannelMetadata\n\n    source = Bridge(sqlite_file_path=channeldbpath)\n\n    ChannelMetadataClass = source.get_class(ChannelMetadata)\n\n    source_channel_metadata = source.session.query(ChannelMetadataClass).all()[0]\n\n    # Use the inferred version from the SQLAlchemy Bridge object, and set it as additional\n    # metadata on the channel data\n\n    source_channel_metadata.inferred_schema_version = source.schema_version\n\n    source.end()\n\n    # Adds an attribute `root_id` when `root_id` does not exist to match with\n    # the latest schema.\n    if not hasattr(source_channel_metadata, \"root_id\"):\n        setattr(\n            source_channel_metadata,\n            \"root_id\",\n            getattr(source_channel_metadata, \"root_pk\"),\n        )\n\n    return source_channel_metadata\n\n\ndef get_channels_for_data_folder(datafolder):\n    channels = []\n    for path in enumerate_content_database_file_paths(\n        get_content_database_dir_path(datafolder)\n    ):\n        try:\n            channel = read_channel_metadata_from_db_file(path)\n        except DatabaseError:\n            logger.warning(\n                \"Tried to import channel from database file {}, but the file was corrupted.\".format(\n                    path\n                )\n            )\n            continue\n        channel_data = {\n            \"path\": path,\n            \"id\": channel.id,\n            \"name\": channel.name,\n            \"description\": channel.description,\n            \"tagline\": getattr(channel, \"tagline\", \"\"),\n            \"thumbnail\": channel.thumbnail,\n            \"version\": channel.version,\n            \"root\": channel.root_id,\n            \"author\": channel.author,\n            \"last_updated\": getattr(channel, \"last_updated\", None),\n            \"lang_code\": getattr(channel, \"lang_code\", None),\n            \"lang_name\": getattr(channel, \"lang_name\", None),\n        }\n        channels.append(channel_data)\n    return channels\n\n\n# Use this to cache mounted drive information when\n# it has already been fetched for querying by drive id\nMOUNTED_DRIVES_CACHE_KEY = \"mounted_drives_cache_key\"\n\n\ndef get_mounted_drives_with_channel_info():\n    drives = enumerate_mounted_disk_partitions()\n    for drive in drives.values():\n        drive.metadata[\"channels\"] = (\n            get_channels_for_data_folder(drive.datafolder) if drive.datafolder else []\n        )\n    cache.set(MOUNTED_DRIVES_CACHE_KEY, drives, 3600)\n    return drives\n\n\ndef get_mounted_drive_by_id(drive_id):\n    drives = cache.get(MOUNTED_DRIVES_CACHE_KEY)\n    if drives is None or drives.get(drive_id, None) is None:\n        drives = get_mounted_drives_with_channel_info()\n    return drives[drive_id]\n", "path": "kolibri/core/content/utils/channels.py"}]}