├── .github └── workflows │ ├── codeql.yml │ └── django.yml ├── .gitignore ├── LICENSE ├── README.es.md ├── README.it.md ├── README.md ├── myproject ├── build.sh ├── manage.py ├── myapp │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── custom_filters.py │ ├── forms.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── static │ │ ├── css │ │ │ ├── countries.css │ │ │ ├── countries.scss │ │ │ └── index.css │ │ ├── img │ │ │ ├── favicon.ico │ │ │ └── github-mark-white.png │ │ ├── js │ │ │ └── countries.js │ │ └── json │ │ │ └── emblems.json │ ├── templates │ │ ├── countries.html │ │ ├── filter_countries.html │ │ └── index.html │ ├── tests.py │ ├── utils.py │ └── views.py ├── myproject │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── requirements.txt └── vercel.json └── screenshots ├── card.gif ├── countries.JPG ├── description.gif ├── filter.gif ├── homepage.JPG └── search.gif /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "Django CI with CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | schedule: 9 | - cron: '00 8 * * 2' 10 | 11 | jobs: 12 | test-django-app: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | max-parallel: 4 17 | matrix: 18 | python-version: [3.12] 19 | 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v4 23 | 24 | - name: Set up Python ${{ matrix.python-version }} 25 | uses: actions/setup-python@v3 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | 29 | - name: Install Dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | cd myproject 33 | pip install -r requirements.txt 34 | 35 | - name: Apply Migrations 36 | run: | 37 | cd myproject 38 | python manage.py makemigrations 39 | python manage.py migrate 40 | 41 | - name: Run Django test 42 | run: | 43 | cd myproject 44 | python manage.py test 45 | 46 | analyze-codeql: 47 | name: Analyze CodeQL (${{ matrix.language }}) 48 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 49 | timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} 50 | permissions: 51 | security-events: write 52 | actions: read 53 | contents: read 54 | 55 | strategy: 56 | fail-fast: false 57 | matrix: 58 | include: 59 | - language: javascript-typescript 60 | build-mode: none 61 | - language: python 62 | build-mode: none 63 | 64 | steps: 65 | - name: Checkout repository 66 | uses: actions/checkout@v4 67 | 68 | - name: Initialize CodeQL 69 | uses: github/codeql-action/init@v3 70 | with: 71 | languages: ${{ matrix.language }} 72 | build-mode: ${{ matrix.build-mode }} 73 | 74 | - name: Perform CodeQL Analysis 75 | uses: github/codeql-action/analyze@v3 76 | with: 77 | category: "/language:${{matrix.language}}" 78 | -------------------------------------------------------------------------------- /.github/workflows/django.yml: -------------------------------------------------------------------------------- 1 | name: Django CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | test-django-app: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | max-parallel: 4 15 | matrix: 16 | python-version: [3.12] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v3 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | 28 | - name: Install Dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | cd myproject 32 | pip install -r requirements.txt 33 | 34 | - name: Apply Migrations 35 | run: | 36 | cd myproject 37 | python manage.py makemigrations 38 | python manage.py migrate 39 | 40 | - name: Run Django test 41 | run: | 42 | cd myproject 43 | python manage.py test 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .vs/ 3 | .env 4 | db.sqlite3 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Davide Presti 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.es.md: -------------------------------------------------------------------------------- 1 | # Rest Countries Info APP [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) ![Python](https://img.shields.io/badge/python-3.12-blue.svg) ![Django](https://img.shields.io/badge/django-5.0.9-darkgreen.svg) 2 | 3 | ![Homepage](screenshots//countries.JPG) 4 | 5 | ## - Introducción - 6 | 7 | **Rest Countries Info APP** es un pequeño proyecto construido con Django que utiliza la [API de REST Countries](https://restcountries.com/) y la [API de Wikipedia](https://www.mediawiki.org/wiki/API:Main_page) para visualizar informaciones detalladas de los países del mundo. Este proyecto se ha desarrollado con Python 3.12, JavaScript, Bootstrap 5, HTML5 y CSS3. 8 | ## - Requisitos - 9 | 10 | Asegúrate de tener Python instalado en tu sístema, para comprobarlo abre la línea de comandos y ejecuta el siguiente comando: 11 | 12 | ```bash 13 | py --version # Windows 14 | python --version # Unix/macOS 15 | ``` 16 | 17 | Si no tienes Python instalado entra en https://www.python.org/downloads/ y descarga la última versión. 18 | 19 | ## - Instalación - 20 | 21 | 1. Clona el repositorio: 22 | 23 | ```bash 24 | git clone https://github.com/davevad93/rest-countries-django-app.git 25 | ``` 26 | 27 | 2. Entra en la carpeta del repositorio: 28 | 29 | ```bash 30 | cd rest-countries-django-app 31 | ``` 32 | 3. Crea un entorno virtual: 33 | 34 | ```bash 35 | py -m venv venv # Windows 36 | python -m venv venv # Unix/macOS 37 | ``` 38 | 39 | 4. Activa el entorno virtual: 40 | 41 | ```bash 42 | venv/Scripts/activate.bat # Windows 43 | source venv/bin/activate # Unix/macOS 44 | ``` 45 | 46 | 5. Entra en la carpeta del proyecto: 47 | 48 | ```bash 49 | cd myproject 50 | ``` 51 | 52 | 6. Instala los paquetes necesarios: 53 | 54 | ```bash 55 | py -m pip install -r requirements.txt # Windows 56 | python -m pip install -r requirements.txt # Unix/macOS 57 | ``` 58 | 59 | 7. Genera una clave secreta aleatoria y guárdala en el archivo .env:
60 | (Esta aplicación no utiliza un sistema de autenticación y no almacena datos sensibles, sin embargo la mejor práctica es crear una clave secreta y almacenarla en una variable de entorno). 61 | 62 | ```bash 63 | # PASO 1: Abre el Shell de Python en el terminal. 64 | 65 | py manage.py shell # Windows 66 | python manage.py shell # Unix/macOS 67 | ``` 68 | 69 | ```bash 70 | # PASO 2: Importa la función get_random_secret_key(). 71 | 72 | from django.core.management.utils import get_random_secret_key 73 | ``` 74 | 75 | ```bash 76 | # PASO 3: Genera la clave secreta con la función get_random_secret_key(). 77 | 78 | print(get_random_secret_key()) 79 | # Copia la clave. 80 | ``` 81 | 82 | ```bash 83 | # PASO 4: Cierra el Shell de Python. 84 | 85 | exit() 86 | ``` 87 | 88 | ```bash 89 | # PASO 5: Crea el archivo .env con la variable de entorno "SECRET KEY" y copia la clave generada en el PASO 3. 90 | 91 | echo SECRET_KEY = 'Pega aquí la clave generada en el PASO 3' > .env 92 | ``` 93 | 94 | 8. Pon en marcha el servidor web de desarrollo: 95 | 96 | ```bash 97 | py manage.py runserver # Windows 98 | python manage.py runserver # Unix/macOS 99 | ``` 100 | 101 | 9. Accede a la aplicación a través de tu browser web en `http://localhost:8000`. 102 | 103 | ![Homepage](screenshots//homepage.JPG) 104 | 105 | ## - Características - 106 | 107 | - ### Busca los países por nombre. 108 | 109 | ![Search](screenshots//search.gif) 110 | 111 | - ### Filtra los países por región. 112 | 113 | ![Filter](screenshots//filter.gif) 114 | 115 | - ### Visualiza las informaciones detalladas de cada país. 116 | 117 | ![Description](screenshots//description.gif) 118 | 119 | - ### Función de búsqueda haciendo clic en las tarjetas. 120 | 121 | ![Card](screenshots//card.gif) 122 | 123 | ## - Anotaciones - 124 | 125 | Puede haber inconsistencias con respecto a la información de ciertos países. Esto se debe a que para mostrar las informaciones básicas de cada país he usado la **API de Rest Countries** (última actualización del 2021) mientras que para mostrar las descripciones de los países he usado la **API de Wikipedia** (actualizada constantemente). Por poner un ejemplo, en Rest Countries aparece China como el páis más poblado, pero en 2022 India la ha superado, siendo a día de hoy el páis con más habitantes en el mundo. 126 | 127 | ## - Desarrollado con - 128 | 129 | - [![Python](https://img.shields.io/badge/Python-14354C?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org) 130 | - [![Django](https://img.shields.io/badge/Django-092E20?style=for-the-badge&logo=django&logoColor=white)](https://www.djangoproject.com) 131 | - [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)](https://www.javascript.com) 132 | - [![Bootstrap](https://img.shields.io/badge/Bootstrap-563D7C?style=for-the-badge&logo=bootstrap&logoColor=white)](https://getbootstrap.com) 133 | - [![HTML](https://img.shields.io/badge/HTML5-E34F26?style=for-the-badge&logo=html5&logoColor=white)](https://html.com) 134 | - [![CSS](https://img.shields.io/badge/CSS3-1572B6?style=for-the-badge&logo=css3&logoColor=white)](https://css3.com) 135 | 136 | ## - Agradecimientos - 137 | 138 | - [REST Countries API](https://restcountries.com/) por los **datos de los países** mostrados en la app. 139 | 140 | - [Wikipedia API](https://www.mediawiki.org/wiki/) por las **descripciones de los países** mostrados en la app. 141 | 142 | - [Font Awesome](https://fontawesome.com/) por el ícono **fa-search** utilizado en la barra de búsqueda de la app. 143 | 144 | - [Awesome Badges](https://github.com/Envoy-VC/awesome-badges) por las insignias en la sección "**Desarrollado con**". 145 | 146 | ## - Atribuciones - 147 | 148 | Algunos escudos de los países mostrados en este proyecto no proceden del [JSON de la API di REST Countries](https://restcountries.com/v3.1/all) porque algunos valores clave están vacíos, así que he exportado los escudos faltantes desde [Wikimedia Commons](https://commons.wikimedia.org/wiki/Main_Page). La mayor parte de ellos están en el dominio público y no necesitan atribución de autoría, pero algunos están bajo licencia de Creative Commons por tanto, voy a citar los autores dándole los créditos. 149 | 150 | - [Demidow](https://commons.wikimedia.org/wiki/User:Demidow), [Coat of arms of the British Indian Ocean Territory](https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_the_British_Indian_Ocean_Territory.svg), [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/legalcode) 151 | 152 | - [Spedona](https://commons.wikimedia.org/wiki/User:Spedona), [Clunies-Ross family arms](https://commons.wikimedia.org/wiki/File:Clunies-Ross_family_arms.svg), [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/legalcode) 153 | 154 | - [Squiresy92](https://commons.wikimedia.org/wiki/User:Squiresy92) & [Sodacan](https://commons.wikimedia.org/wiki/User:Sodacan), [Coat of arms of Norfolk Island](https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_Norfolk_Island.svg), [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode) 155 | 156 | - [Heralder](https://commons.wikimedia.org/wiki/User:Heralder), [Coat of arms of the Commonwealth of Puerto Rico](https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_the_Commonwealth_of_Puerto_Rico.svg), [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/legalcode) 157 | 158 | - [Superbenjamin](https://commons.wikimedia.org/wiki/User:Superbenjamin), [Armoiries Réunion](https://commons.wikimedia.org/wiki/File:Armoiries_R%C3%A9union.svg), [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode) 159 | 160 | - [Manassas](https://commons.wikimedia.org/wiki/User_talk:Manassas~commonswiki), [Blason St Barthélémy TOM entire](https://commons.wikimedia.org/wiki/File:Blason_St_Barth%C3%A9l%C3%A9my_TOM_entire.svg), [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/legalcode) 161 | 162 | 163 | - [Di (they-them)](https://commons.wikimedia.org/wiki/User:Di_(they-them)), [Coat of Arms of Saint Helena](https://commons.wikimedia.org/wiki/File:Coat_of_Arms_of_Saint_Helena.svg), [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode) 164 | 165 | - [Government of the Collectivity of Saint-Martin](https://www.com-saint-martin.fr), [Local flag of the Collectivity of Saint Martin](https://commons.wikimedia.org/wiki/File:Local_flag_of_the_Collectivity_of_Saint_Martin.svg), [Licence Ouverte 2.0](https://www.etalab.gouv.fr/wp-content/uploads/2018/11/open-licence.pdf) 166 | 167 | - [Josedar](https://commons.wikimedia.org/wiki/User:Josedar), [Coat of arms of the Turks and Caicos Islands](https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_the_Turks_and_Caicos_Islands.svg), [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode) 168 | 169 | ## - Contribuir al proyecto - 170 | 171 | Cualquier tipo de aportación es bienvenida! Si encuentras algún problema o tienes algúna sugerencia para mejorar el proyecto, por favor abre una issue o forkea el repositorio y haz una pull request. No te olvides de dar una estrella al proyecto. Muchísimas gracias! 172 | 173 | ## - Licencia - 174 | 175 | Este proyecto está bajo licencia MIT. Para obtener más información, consulta el archivo de [LICENCIA](LICENSE). 176 | -------------------------------------------------------------------------------- /README.it.md: -------------------------------------------------------------------------------- 1 | # Rest Countries Info APP [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) ![Python](https://img.shields.io/badge/python-3.12-blue.svg) ![Django](https://img.shields.io/badge/django-5.0.9-darkgreen.svg) 2 | 3 | ![Homepage](screenshots//countries.JPG) 4 | 5 | ## - Introduzione - 6 | 7 | **Rest Countries Info APP** è un piccolo progetto costruito con Django che utilizza la [API di REST Countries](https://restcountries.com/) e la [API di Wikipedia](https://www.mediawiki.org/wiki/API:Main_page) per visualizzare le informazioni dettagliate dei paesi del mondo. Questo progetto è stato sviluppato con Python 3.12, JavaScript, Bootstrap 5, HTML5 e CSS3. 8 | 9 | ## - Requisiti - 10 | 11 | Assicurati di avere Python installato nel tuo sistema, per verificarlo apri la riga di comando ed esegui questo comando: 12 | 13 | ```bash 14 | py --version # Windows 15 | python --version # Unix/macOS 16 | ``` 17 | 18 | Se non hai Python installato visita https://www.python.org/downloads/ e scarica l'ultima versione. 19 | 20 | ## - Installazione - 21 | 22 | 1. Clona la repository: 23 | 24 | ```bash 25 | git clone https://github.com/davevad93/rest-countries-django-app.git 26 | ``` 27 | 28 | 2. Entra nella cartella della repository: 29 | 30 | ```bash 31 | cd rest-countries-django-app 32 | ``` 33 | 3. Crea un ambiente virtuale: 34 | 35 | ```bash 36 | py -m venv venv # Windows 37 | python -m venv venv # Unix/macOS 38 | ``` 39 | 40 | 4. Attiva l'ambiente virtuale: 41 | 42 | ```bash 43 | venv/Scripts/activate.bat # Windows 44 | source venv/bin/activate # Unix/macOS 45 | ``` 46 | 47 | 5. Entra nella cartella del progetto: 48 | 49 | ```bash 50 | cd myproject 51 | ``` 52 | 53 | 6. Installa i pacchetti necessari: 54 | 55 | ```bash 56 | py -m pip install -r requirements.txt # Windows 57 | python -m pip install -r requirements.txt # Unix/macOS 58 | ``` 59 | 60 | 7. Genera una chiave segreta casuale e salvala in un file .env:
61 | (Questa app non utilizza un metodo di autenticazione e non immagazzina dati sensibili, ma in ogni caso è sempre meglio creare una chiave segreta e salvarla in una variabile d'ambiente). 62 | 63 | ```bash 64 | # PASSO 1: Apri la Shell di Python nel terminale. 65 | 66 | py manage.py shell # Windows 67 | python manage.py shell # Unix/macOS 68 | ``` 69 | 70 | ```bash 71 | # PASSO 2: Importa la funzione get_random_secret_key(). 72 | 73 | from django.core.management.utils import get_random_secret_key 74 | ``` 75 | 76 | ```bash 77 | # PASSO 3: Genera la chiave segreta con la funzione get_random_secret_key(). 78 | 79 | print(get_random_secret_key()) 80 | # Copia la chiave. 81 | ``` 82 | 83 | ```bash 84 | # PASSO 4: Chiudi la Shell di Python. 85 | 86 | exit() 87 | ``` 88 | 89 | ```bash 90 | # PASSO 5: Crea il file .env con la variabile d'ambiente "SECRET KEY" e copia la chiave generata nel PASSO 3. 91 | 92 | echo SECRET_KEY = 'Incolla la chiave generata nel PASSO 3' > .env 93 | ``` 94 | 95 | 8. Avvia il server di sviluppo: 96 | 97 | ```bash 98 | py manage.py runserver # Windows 99 | python manage.py runserver # Unix/macOS 100 | ``` 101 | 102 | 9. Accedi all'applicazione con il tuo browser web visitando `http://localhost:8000`. 103 | 104 | ![Homepage](screenshots//homepage.JPG) 105 | 106 | ## - Caratteristiche - 107 | 108 | - ### Cerca i paesi per nome. 109 | 110 | ![Search](screenshots//search.gif) 111 | 112 | - ### Filtra i paesi per regione. 113 | 114 | ![Filter](screenshots//filter.gif) 115 | 116 | - ### Visualizza le informazioni dettagliate di ogni paese. 117 | 118 | ![Description](screenshots//description.gif) 119 | 120 | - ### Funzione di ricerca cliccando sulle tessere. 121 | 122 | ![Card](/screenshots//card.gif) 123 | 124 | ## - Note aggiuntive - 125 | 126 | Potrebbero esserci delle inconsistenze riguardo alle informazioni di certi paesi. Questo è dovuto al fatto che per mostrare le informazioni basiche di ogni paese (p. e. popolazione) ho usato la **API di Rest Countries** (ultimo aggiornamento nel 2021) mentre per mostrare le descrizioni dei paesi ho usato la **API di Wikipedia** (che viene aggiornata costantemente). Per fare un esempio su Rest Countries la Cina risulta ancora come il paese più popolato, peró nel 2022 l'India la ha superato, essendo al giorno d'oggi il paese con più abitanti al mondo. 127 | 128 | ## - Sviluppato con - 129 | 130 | - [![Python](https://img.shields.io/badge/Python-14354C?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org) 131 | - [![Django](https://img.shields.io/badge/Django-092E20?style=for-the-badge&logo=django&logoColor=white)](https://www.djangoproject.com) 132 | - [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)](https://www.javascript.com) 133 | - [![Bootstrap](https://img.shields.io/badge/Bootstrap-563D7C?style=for-the-badge&logo=bootstrap&logoColor=white)](https://getbootstrap.com) 134 | - [![HTML](https://img.shields.io/badge/HTML5-E34F26?style=for-the-badge&logo=html5&logoColor=white)](https://html.com) 135 | - [![CSS](https://img.shields.io/badge/CSS3-1572B6?style=for-the-badge&logo=css3&logoColor=white)](https://css3.com) 136 | 137 | ## - Riconoscimenti - 138 | 139 | - [REST Countries API](https://restcountries.com/) per i **dati dei paesi** mostrati nella app. 140 | 141 | - [Wikipedia API](https://www.mediawiki.org/wiki/) per le **descrizioni dei paesi** mostrati nella app. 142 | 143 | - [Font Awesome](https://fontawesome.com/) per l'icona **fa-search** utilizzata nella barra di ricerca della app. 144 | 145 | - [Awesome Badges](https://github.com/Envoy-VC/awesome-badges) per le badge della sezione "**Sviluppato con**". 146 | 147 | ## - Crediti - 148 | 149 | Alcuni stemmi dei paesi mostrati in questo progetto non provengono dal [JSON della API di REST Countries](https://restcountries.com/v3.1/all) perche certi valori chiave sono vuoti, quindi ho esportato gli stemmi mancanti da [Wikimedia Commons](https://commons.wikimedia.org/wiki/Main_Page). La maggior parte di essi sono di dominio pubblico e non hanno bisogno di attribuzione d'autore ma alcuni sono sotto licenza di Creative Commons, perciò ne elencherò gli autori dando i dovuti crediti. 150 | 151 | - [Demidow](https://commons.wikimedia.org/wiki/User:Demidow), [Coat of arms of the British Indian Ocean Territory](https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_the_British_Indian_Ocean_Territory.svg), [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/legalcode) 152 | 153 | - [Spedona](https://commons.wikimedia.org/wiki/User:Spedona), [Clunies-Ross family arms](https://commons.wikimedia.org/wiki/File:Clunies-Ross_family_arms.svg), [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/legalcode) 154 | 155 | - [Squiresy92](https://commons.wikimedia.org/wiki/User:Squiresy92) & [Sodacan](https://commons.wikimedia.org/wiki/User:Sodacan), [Coat of arms of Norfolk Island](https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_Norfolk_Island.svg), [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode) 156 | 157 | - [Heralder](https://commons.wikimedia.org/wiki/User:Heralder), [Coat of arms of the Commonwealth of Puerto Rico](https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_the_Commonwealth_of_Puerto_Rico.svg), [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/legalcode) 158 | 159 | - [Superbenjamin](https://commons.wikimedia.org/wiki/User:Superbenjamin), [Armoiries Réunion](https://commons.wikimedia.org/wiki/File:Armoiries_R%C3%A9union.svg), [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode) 160 | 161 | - [Manassas](https://commons.wikimedia.org/wiki/User_talk:Manassas~commonswiki), [Blason St Barthélémy TOM entire](https://commons.wikimedia.org/wiki/File:Blason_St_Barth%C3%A9l%C3%A9my_TOM_entire.svg), [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/legalcode) 162 | 163 | - [Di (they-them)](https://commons.wikimedia.org/wiki/User:Di_(they-them)), [Coat of Arms of Saint Helena](https://commons.wikimedia.org/wiki/File:Coat_of_Arms_of_Saint_Helena.svg), [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode) 164 | 165 | - [Government of the Collectivity of Saint-Martin](https://www.com-saint-martin.fr), [Local flag of the Collectivity of Saint Martin](https://commons.wikimedia.org/wiki/File:Local_flag_of_the_Collectivity_of_Saint_Martin.svg), [Licence Ouverte 2.0](https://www.etalab.gouv.fr/wp-content/uploads/2018/11/open-licence.pdf) 166 | 167 | - [Josedar](https://commons.wikimedia.org/wiki/User:Josedar), [Coat of arms of the Turks and Caicos Islands](https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_the_Turks_and_Caicos_Islands.svg), [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode) 168 | 169 | ## - Contribuire al progetto - 170 | 171 | Ogni tipo di contributo è benvenuto! Se trovi qualche problema o hai un suggerimento per migliorare il progetto, per favore apri una issue o esegui il fork del repository e fai una pull request. Non dimenticare di dare una stella al progetto. Grazie mille! 172 | 173 | ## - Licenza - 174 | 175 | Questo progetto è sotto Licenza MIT. Per saperne di più, dai un'occhiata al file di [LICENZA](LICENSE). 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rest Countries Info APP [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) ![Python](https://img.shields.io/badge/python-3.12-blue.svg) ![Django](https://img.shields.io/badge/django-5.0.9-darkgreen.svg) 2 | 3 | ![Homepage](screenshots//countries.JPG) 4 | 5 | ## - Introduction - 6 | 7 | **Rest Countries Info APP** is a small Django project that utilize [REST Countries API](https://restcountries.com/) and [Wikipedia API](https://www.mediawiki.org/wiki/API:Main_page) to provide specific information about countries around the world. The project is built using Python 3.12, JavaScript, Bootstrap 5, HTML5 and CSS3. 8 | 9 | ## - Requirements - 10 | 11 | Make sure you have Python installed on your system, you can check this by running on the command line: 12 | 13 | ```bash 14 | py --version # Windows 15 | python --version # Unix/macOS 16 | ``` 17 | 18 | If you don't have Python installed go to https://www.python.org/downloads/ and download the latest version. 19 | 20 | ## - Installation - 21 | 22 | 1. Clone the repository: 23 | 24 | ```bash 25 | git clone https://github.com/davevad93/rest-countries-django-app.git 26 | ``` 27 | 28 | 2. Go to the repo directory: 29 | 30 | ```bash 31 | cd rest-countries-django-app 32 | ``` 33 | 3. Create a virtual environment: 34 | 35 | ```bash 36 | py -m venv venv # Windows 37 | python -m venv venv # Unix/macOS 38 | ``` 39 | 40 | 4. Activate the virtual environment: 41 | 42 | ```bash 43 | venv/Scripts/activate.bat # Windows 44 | source venv/bin/activate # Unix/macOS 45 | ``` 46 | 47 | 5. Go to the project directory: 48 | 49 | ```bash 50 | cd myproject 51 | ``` 52 | 53 | 6. Install the required dependencies: 54 | 55 | ```bash 56 | py -m pip install -r requirements.txt # Windows 57 | python -m pip install -r requirements.txt # Unix/macOS 58 | ``` 59 | 60 | 7. Generate a random Secret Key and store it inside the .env file:
61 | (This app doesn't utilize an authentication system and doesn't store any sensitive data, however it is always a good habit to generate a Secret Key and store it in your enviroment file). 62 | 63 | ```bash 64 | # STEP 1: Open the Python Shell within the terminal. 65 | 66 | py manage.py shell # Windows 67 | python manage.py shell # Unix/macOS 68 | ``` 69 | 70 | ```bash 71 | # STEP 2: Import the get_random_secret_key() function. 72 | 73 | from django.core.management.utils import get_random_secret_key 74 | ``` 75 | 76 | ```bash 77 | # STEP 3: Generate the Secret Key using the get_random_secret_key() function. 78 | 79 | print(get_random_secret_key()) 80 | # Copy the key. 81 | ``` 82 | 83 | ```bash 84 | # STEP 4: Exit the Python Shell. 85 | 86 | exit() 87 | ``` 88 | 89 | ```bash 90 | # STEP 5: Create .env file with the "SECRET_KEY" environment variable and paste the generated key in STEP 3. 91 | 92 | echo SECRET_KEY = 'Paste here the key generated in STEP 3' > .env 93 | ``` 94 | 95 | 8. Run the development server: 96 | 97 | ```bash 98 | py manage.py runserver # Windows 99 | python manage.py runserver # Unix/macOS 100 | ``` 101 | 102 | 9. Access the application in your web browser at `http://localhost:8000`. 103 | 104 | ![Homepage](screenshots//homepage.JPG) 105 | 106 | ## - Features - 107 | 108 | - ### Search for countries by name. 109 | 110 | ![Search](screenshots//search.gif) 111 | 112 | - ### Filter for countries by region. 113 | 114 | ![Filter](screenshots//filter.gif) 115 | 116 | - ### View detailed information about each country. 117 | 118 | ![Description](screenshots//description.gif) 119 | 120 | - ### Search feature by clicking on the cards. 121 | 122 | ![Card](screenshots//card.gif) 123 | 124 | ## - Notes - 125 | 126 | There may be some inconsistencies about the information of certain countries. This is due to the fact that to display the basic information of each country (e.g. population) I've used the **REST Countries API** (last updated in 2021) while to display countries descriptions I've used the **Wikipedia API** (which is constantly updated). To give an example, in the REST countries data, the most populated country in the world is China, but In 2022 India overtook it as the world's country with the largest population. 127 | 128 | ## - Made with - 129 | 130 | - [![Python](https://img.shields.io/badge/Python-14354C?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org) 131 | - [![Django](https://img.shields.io/badge/Django-092E20?style=for-the-badge&logo=django&logoColor=white)](https://www.djangoproject.com) 132 | - [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)](https://www.javascript.com) 133 | - [![Bootstrap](https://img.shields.io/badge/Bootstrap-563D7C?style=for-the-badge&logo=bootstrap&logoColor=white)](https://getbootstrap.com) 134 | - [![HTML](https://img.shields.io/badge/HTML5-E34F26?style=for-the-badge&logo=html5&logoColor=white)](https://html.com) 135 | - [![CSS](https://img.shields.io/badge/CSS3-1572B6?style=for-the-badge&logo=css3&logoColor=white)](https://css3.com) 136 | 137 | ## - Acknowledgments - 138 | 139 | - [REST Countries API](https://restcountries.com/) for the general **country data** showed in the app. 140 | 141 | - [Wikipedia API](https://www.mediawiki.org/wiki/) for the **country descriptions** displayed in the app. 142 | 143 | - [Font Awesome](https://fontawesome.com/) for the **fa-search** icon used in the app search bar. 144 | 145 | - [Awesome Badges](https://github.com/Envoy-VC/awesome-badges) for the badges used in the "**Made with**" section. 146 | 147 | ## - Credits - 148 | 149 | A few country emblems displayed in this project aren't from the [REST Countries API JSON](https://restcountries.com/v3.1/all) because some key values are null, so I have fetched the missing ones from [Wikimedia Commons](https://commons.wikimedia.org/wiki/Main_Page). Most of them are in the public domain and don't need attribution but some are under Creative Commons licenses hence, I'm going to give the due credit to the authors. 150 | 151 | - [Demidow](https://commons.wikimedia.org/wiki/User:Demidow), [Coat of arms of the British Indian Ocean Territory](https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_the_British_Indian_Ocean_Territory.svg), [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/legalcode) 152 | 153 | - [Spedona](https://commons.wikimedia.org/wiki/User:Spedona), [Clunies-Ross family arms](https://commons.wikimedia.org/wiki/File:Clunies-Ross_family_arms.svg), [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/legalcode) 154 | 155 | - [Squiresy92](https://commons.wikimedia.org/wiki/User:Squiresy92) & [Sodacan](https://commons.wikimedia.org/wiki/User:Sodacan), [Coat of arms of Norfolk Island](https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_Norfolk_Island.svg), [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode) 156 | 157 | - [Heralder](https://commons.wikimedia.org/wiki/User:Heralder), [Coat of arms of the Commonwealth of Puerto Rico](https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_the_Commonwealth_of_Puerto_Rico.svg), [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/legalcode) 158 | 159 | - [Superbenjamin](https://commons.wikimedia.org/wiki/User:Superbenjamin), [Armoiries Réunion](https://commons.wikimedia.org/wiki/File:Armoiries_R%C3%A9union.svg), [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode) 160 | 161 | - [Manassas](https://commons.wikimedia.org/wiki/User_talk:Manassas~commonswiki), [Blason St Barthélémy TOM entire](https://commons.wikimedia.org/wiki/File:Blason_St_Barth%C3%A9l%C3%A9my_TOM_entire.svg), [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/legalcode) 162 | 163 | 164 | - [Di (they-them)](https://commons.wikimedia.org/wiki/User:Di_(they-them)), [Coat of Arms of Saint Helena](https://commons.wikimedia.org/wiki/File:Coat_of_Arms_of_Saint_Helena.svg), [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode) 165 | 166 | - [Government of the Collectivity of Saint-Martin](https://www.com-saint-martin.fr), [Local flag of the Collectivity of Saint Martin](https://commons.wikimedia.org/wiki/File:Local_flag_of_the_Collectivity_of_Saint_Martin.svg), [Licence Ouverte 2.0](https://www.etalab.gouv.fr/wp-content/uploads/2018/11/open-licence.pdf) 167 | 168 | - [Josedar](https://commons.wikimedia.org/wiki/User:Josedar), [Coat of arms of the Turks and Caicos Islands](https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_the_Turks_and_Caicos_Islands.svg), [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode) 169 | 170 | ## - Contributing - 171 | 172 | Contributions are truly welcome! If you find any issues or have suggestions for improvements, please open an issue or fork the repository and submit a pull request. Don't forget to give this project a star. Thank you very much! 173 | 174 | ## - License - 175 | 176 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information. 177 | -------------------------------------------------------------------------------- /myproject/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit on error 4 | set -o errexit 5 | 6 | # Install packages 7 | pip install -r requirements.txt 8 | 9 | # Convert static asset files 10 | python manage.py collectstatic --no-input 11 | 12 | # Apply database migrations 13 | python manage.py migrate 14 | -------------------------------------------------------------------------------- /myproject/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /myproject/myapp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davevad93/rest-countries-django-app/2f49ffbab8135c732a7a54a1cefd3aa265aa3711/myproject/myapp/__init__.py -------------------------------------------------------------------------------- /myproject/myapp/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /myproject/myapp/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | class RestCountriesInfoAppConfig(AppConfig): 4 | default_auto_field = 'django.db.models.BigAutoField' 5 | name = 'myapp' 6 | -------------------------------------------------------------------------------- /myproject/myapp/custom_filters.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from django import template 4 | from django.conf import settings 5 | 6 | register = template.Library() 7 | 8 | @register.filter 9 | def get_emblems_url(country): 10 | file_path = os.path.join(settings.BASE_DIR, 'myapp', 'static', 'json', 'emblems.json') 11 | with open(file_path, 'r', encoding='utf-8') as json_file: 12 | data = json.load(json_file) 13 | for info in data: 14 | if info['country']['name'] == country: 15 | return info['country']['url'] 16 | return None 17 | -------------------------------------------------------------------------------- /myproject/myapp/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | class CountrySearchForm(forms.Form): 4 | search_term = forms.CharField( 5 | max_length=100, 6 | required=False, 7 | widget=forms.TextInput(attrs={ 8 | 'placeholder': 'Search a country...' 9 | }) 10 | ) 11 | 12 | class RegionFilterForm(forms.Form): 13 | regions = forms.ChoiceField( 14 | choices=( 15 | ('All', 'All'), 16 | ('Africa', 'Africa'), 17 | ('Americas', 'Americas'), 18 | ('Antarctic', 'Antarctic'), 19 | ('Asia', 'Asia'), 20 | ('Europe', 'Europe'), 21 | ('Oceania', 'Oceania'), 22 | ), 23 | required=False, 24 | widget=forms.Select(attrs={'aria-label': 'Select region'}), 25 | ) 26 | -------------------------------------------------------------------------------- /myproject/myapp/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davevad93/rest-countries-django-app/2f49ffbab8135c732a7a54a1cefd3aa265aa3711/myproject/myapp/migrations/__init__.py -------------------------------------------------------------------------------- /myproject/myapp/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | class Region(models.Model): 4 | name = models.CharField(max_length=100) 5 | 6 | def __str__(self): 7 | return self.name 8 | 9 | class Country(models.Model): 10 | name = models.CharField(max_length=100) 11 | capital = models.CharField(max_length=100) 12 | population = models.IntegerField() 13 | region = models.ForeignKey(Region, on_delete=models.CASCADE) 14 | subregion = models.CharField(max_length=100) 15 | currency = models.CharField(max_length=100) 16 | language = models.CharField(max_length=100) 17 | borders = models.CharField(max_length=100) 18 | area = models.FloatField() 19 | timezone = models.CharField(max_length=100) 20 | top_level_domain = models.CharField(max_length=100) 21 | 22 | def __str__(self): 23 | return self.name 24 | -------------------------------------------------------------------------------- /myproject/myapp/static/css/countries.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | html, 3 | body { 4 | margin: 0; 5 | padding: 0; 6 | background-color: #E5E4E2; 7 | -moz-box-sizing: border-box; 8 | -webkit-box-sizing: border-box; 9 | box-sizing: border-box; 10 | } 11 | html header div, 12 | body header div { 13 | top: 0; 14 | left: 0; 15 | right: 0; 16 | height: 15vh; 17 | padding: 1.5rem 1rem; 18 | display: flex; 19 | color: white; 20 | position: fixed; 21 | white-space: nowrap; 22 | align-items: center; 23 | background-color: #393939; 24 | justify-content: space-between; 25 | font-family: Cambria, Cochin, Georgia, Times, "Times New Roman", serif; 26 | } 27 | html header div h1, 28 | body header div h1 { 29 | margin: 0; 30 | font-size: 1.37rem; 31 | text-shadow: 4px 4px black; 32 | font-weight: bold; 33 | font-family: American Trypewriter, sans-serif; 34 | } 35 | html header div form, 36 | body header div form { 37 | margin: 1rem; 38 | display: flex; 39 | align-items: center; 40 | } 41 | html header div #id_search_term, 42 | html header div #id_regions, 43 | html header div #back-button, 44 | html header div #back-button:after, 45 | body header div #id_search_term, 46 | body header div #id_regions, 47 | body header div #back-button, 48 | body header div #back-button:after { 49 | -moz-border-radius: 10px; 50 | -webkit-border-radius: 10px; 51 | border-radius: 10px; 52 | } 53 | html header div #id_search_term, 54 | body header div #id_search_term { 55 | width: 400px; 56 | height: 35px; 57 | padding: 10px; 58 | margin-right: 55px; 59 | } 60 | html header div #search-glass, 61 | body header div #search-glass { 62 | margin-left: 370px; 63 | border-radius: 50%; 64 | border: none; 65 | cursor: pointer; 66 | position: fixed; 67 | background: none; 68 | color: gray; 69 | } 70 | html header div #search-glass:hover, 71 | body header div #search-glass:hover { 72 | color: #393939; 73 | } 74 | html header div #id_regions, 75 | body header div #id_regions { 76 | width: 120px; 77 | height: 30px; 78 | padding-left: 10px; 79 | background-color: white; 80 | } 81 | html header div #back-button, 82 | body header div #back-button { 83 | width: 100px; 84 | height: 30px; 85 | font-size: 15px; 86 | margin-left: 50px; 87 | line-height: 15px; 88 | display: block; 89 | position: relative; 90 | transition: 0.3s; 91 | text-decoration: none; 92 | border: 2px solid #fff; 93 | box-shadow: 5px 5px black; 94 | background-color: #585858; 95 | } 96 | html header div #back-button:active, 97 | body header div #back-button:active { 98 | transform: scale(0.98); 99 | box-shadow: 3px 2px 22px 1px rgba(0, 0, 0, 0.24); 100 | } 101 | html header div #back-button:after, 102 | body header div #back-button:after { 103 | top: 0; 104 | right: 0; 105 | width: 0; 106 | height: 100%; 107 | line-height: 25px; 108 | color: #393939; 109 | content: "⟲"; 110 | position: absolute; 111 | transition: all 0.5s; 112 | background: ghostwhite; 113 | } 114 | html header div #back-button:hover:after, 115 | body header div #back-button:hover:after { 116 | width: 100%; 117 | } 118 | html header div .search-dropdown, 119 | body header div .search-dropdown { 120 | z-index: 1; 121 | top: 9.5vh; 122 | width: 400px; 123 | margin-left: -488px; 124 | border-radius: 10px; 125 | box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); 126 | display: none; 127 | border-top: none; 128 | position: absolute; 129 | list-style-type: none; 130 | border: 1px solid #ccc; 131 | background-color: #fff; 132 | } 133 | html header div .search-item, 134 | body header div .search-item { 135 | padding: 8px 16px; 136 | color: black; 137 | cursor: pointer; 138 | } 139 | html header div .search-item:hover, 140 | body header div .search-item:hover { 141 | border-radius: 10px; 142 | background-color: #E5E4E2; 143 | } 144 | html header div .active, 145 | body header div .active { 146 | border-radius: 10px; 147 | background-color: #E5E4E2; 148 | } 149 | html main #country-container, 150 | body main #country-container { 151 | padding: 2rem; 152 | grid-gap: 2rem; 153 | margin-top: 15vh; 154 | min-height: 70vh; 155 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 156 | display: grid; 157 | align-items: flex-start; 158 | background-color: #E5E4E2; 159 | } 160 | html main #country-container #country-card, 161 | html main #country-container #description-card, 162 | html main #country-container .emblem-card, 163 | body main #country-container #country-card, 164 | body main #country-container #description-card, 165 | body main #country-container .emblem-card { 166 | padding: 1.5rem; 167 | font-size: 0.9rem; 168 | min-height: 444px; 169 | max-height: 444px; 170 | border-radius: 10px; 171 | box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2); 172 | display: flex; 173 | overflow-y: auto; 174 | overflow-x: hidden; 175 | text-align: inherit; 176 | align-items: center; 177 | flex-direction: column; 178 | background-color: white; 179 | font-family: Cambria, Cochin, Georgia, Times, "Times New Roman", serif; 180 | } 181 | html main #country-container #country-card h1, 182 | body main #country-container #country-card h1 { 183 | margin-top: 15px; 184 | font-size: 1rem; 185 | text-align: center; 186 | font-weight: bold; 187 | } 188 | html main #country-container #description-card, 189 | body main #country-container #description-card { 190 | font-size: 1rem; 191 | line-height: 1.2; 192 | } 193 | html main #country-container #description-card h1, 194 | body main #country-container #description-card h1 { 195 | margin-top: 15px; 196 | font-size: 1.1rem; 197 | font-weight: bold; 198 | } 199 | html main #country-container .emblem-card, 200 | body main #country-container .emblem-card { 201 | pointer-events: none; 202 | justify-content: center; 203 | } 204 | html main #country-container #country-card:hover, 205 | body main #country-container #country-card:hover { 206 | cursor: pointer; 207 | background-color: ghostwhite; 208 | } 209 | html main #country-container #country-card.searched:hover, 210 | body main #country-container #country-card.searched:hover { 211 | cursor: default; 212 | background-color: white; 213 | } 214 | html main #country-container #img, 215 | body main #country-container #img { 216 | width: 225px; 217 | height: 135px; 218 | border-radius: 10px; 219 | border-style: ridge; 220 | pointer-events: none; 221 | border-color: #C0C0C0; 222 | } 223 | html main #country-container .img2, 224 | body main #country-container .img2 { 225 | height: 60px; 226 | width: 60px; 227 | border-radius: 50%; 228 | object-fit: contain; 229 | border-style: ridge; 230 | border-color: #C0C0C0; 231 | background-color: #E5E4E2; 232 | } 233 | html main #country-container .img3, 234 | body main #country-container .img3 { 235 | min-width: 100%; 236 | min-height: 100%; 237 | aspect-ratio: 0.5; 238 | object-fit: contain; 239 | } 240 | html main #country-container #country-name, 241 | body main #country-container #country-name { 242 | font-size: 15px; 243 | } 244 | html main #country-container #no-results h1, 245 | body main #country-container #no-results h1 { 246 | margin-top: 0; 247 | font-size: 1.5rem; 248 | font-weight: bold; 249 | white-space: nowrap; 250 | } 251 | html footer div, 252 | body footer div { 253 | height: 15vh; 254 | padding: 1.5rem 1rem; 255 | text-shadow: 2px 2px black; 256 | color: white; 257 | display: flex; 258 | align-items: center; 259 | white-space: nowrap; 260 | background-color: #393939; 261 | justify-content: space-between; 262 | font-family: Cambria, Cochin, Georgia, Times, "Times New Roman", serif; 263 | } 264 | html footer div a, 265 | html footer div a:link, 266 | html footer div a:visited, 267 | html footer div a:active, 268 | body footer div a, 269 | body footer div a:link, 270 | body footer div a:visited, 271 | body footer div a:active { 272 | color: white; 273 | font-weight: bold; 274 | text-decoration: none; 275 | } 276 | html footer div a, 277 | html footer div img, 278 | body footer div a, 279 | body footer div img { 280 | opacity: 0.9; 281 | } 282 | html footer div a:hover, 283 | html footer div img:hover, 284 | body footer div a:hover, 285 | body footer div img:hover { 286 | opacity: 1; 287 | } 288 | html footer div p, 289 | body footer div p { 290 | margin-top: 1rem; 291 | } 292 | html footer div #logo, 293 | body footer div #logo { 294 | width: 25px; 295 | margin-right: 2rem; 296 | } 297 | 298 | @media only screen and (min-width: 1025px) and (max-width: 1368px) { 299 | html header div .search-dropdown, 300 | body header div .search-dropdown { 301 | margin-left: -487px; 302 | } 303 | html main #country-container, 304 | body main #country-container { 305 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 306 | } 307 | } 308 | @media only screen and (max-width: 1024px) { 309 | html header div h1, 310 | body header div h1 { 311 | font-size: 1.2rem; 312 | } 313 | html header div form #id_search_term, 314 | body header div form #id_search_term { 315 | margin-left: 100px; 316 | } 317 | html header div form #search-glass, 318 | body header div form #search-glass { 319 | margin-left: 470px; 320 | } 321 | html header div form #id_regions, 322 | body header div form #id_regions { 323 | width: 110px; 324 | margin-right: 150px; 325 | } 326 | html main #country-container, 327 | body main #country-container { 328 | padding: 1.5rem; 329 | grid-gap: 1.5rem; 330 | } 331 | html main #country-container #country-card, 332 | html main #country-container #description-card, 333 | html main #country-container .emblem-card, 334 | body main #country-container #country-card, 335 | body main #country-container #description-card, 336 | body main #country-container .emblem-card { 337 | min-height: 430px; 338 | max-height: 430px; 339 | } 340 | html main #country-container #country-card h1, 341 | body main #country-container #country-card h1 { 342 | font-size: 1rem; 343 | } 344 | html main #country-container #description-card, 345 | body main #country-container #description-card { 346 | width: 912px; 347 | } 348 | html main #country-container #description-card h1, 349 | body main #country-container #description-card h1 { 350 | font-size: 1.1rem; 351 | } 352 | html main #country-container #img, 353 | body main #country-container #img { 354 | width: 200px; 355 | height: 120px; 356 | } 357 | html main #country-container .img2, 358 | body main #country-container .img2 { 359 | height: 50px; 360 | width: 50px; 361 | } 362 | html main #country-container #country-name, 363 | body main #country-container #country-name { 364 | font-size: 15px; 365 | } 366 | html main #country-container #no-results h1, 367 | body main #country-container #no-results h1 { 368 | font-size: 1.5rem; 369 | } 370 | html footer div, 371 | body footer div { 372 | padding: 1.5rem; 373 | font-size: 0.9rem; 374 | } 375 | html footer div #logo, 376 | body footer div #logo { 377 | width: 25px; 378 | } 379 | } 380 | @media only screen and (max-width: 993px) { 381 | html header div h1, 382 | body header div h1 { 383 | display: none; 384 | } 385 | html header div form #id_search_term, 386 | body header div form #id_search_term { 387 | width: 340px; 388 | margin-left: 0; 389 | } 390 | html header div form .search-dropdown, 391 | body header div form .search-dropdown { 392 | width: 340px; 393 | margin-left: -428px; 394 | } 395 | html header div form #search-glass, 396 | body header div form #search-glass { 397 | display: none; 398 | } 399 | html header div form #id_regions, 400 | body header div form #id_regions { 401 | margin-right: 0; 402 | } 403 | html main #country-container, 404 | body main #country-container { 405 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 406 | } 407 | html main #country-container #description-card, 408 | body main #country-container #description-card { 409 | width: 672px; 410 | } 411 | } 412 | @media only screen and (min-width: 992px) and (max-width: 993px) { 413 | html main #country-container, 414 | body main #country-container { 415 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 416 | } 417 | html main #country-container #description-card, 418 | body main #country-container #description-card { 419 | width: 912px; 420 | } 421 | } 422 | @media only screen and (max-width: 769px) { 423 | html header div form, 424 | body header div form { 425 | font-size: 1rem; 426 | } 427 | html header div form #id_search_term, 428 | body header div form #id_search_term { 429 | width: 340px; 430 | } 431 | html header div form #search-glass, 432 | body header div form #search-glass { 433 | display: none; 434 | } 435 | html main #country-container, 436 | body main #country-container { 437 | padding: 1rem; 438 | grid-gap: 1rem; 439 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 440 | } 441 | html main #country-container #country-card, 442 | html main #country-container #description-card, 443 | html main #country-container .emblem-card, 444 | body main #country-container #country-card, 445 | body main #country-container #description-card, 446 | body main #country-container .emblem-card { 447 | padding: 1rem; 448 | font-size: 0.9rem; 449 | min-height: 370px; 450 | max-height: 370px; 451 | } 452 | html main #country-container #country-card h1, 453 | body main #country-container #country-card h1 { 454 | font-size: 0.9rem; 455 | } 456 | html main #country-container #description-card, 457 | body main #country-container #description-card { 458 | width: 690px; 459 | } 460 | html main #country-container #description-card h1, 461 | body main #country-container #description-card h1 { 462 | font-size: 1rem; 463 | } 464 | html main #country-container #img, 465 | body main #country-container #img { 466 | width: 150px; 467 | height: 90px; 468 | } 469 | html main #country-container .img2, 470 | body main #country-container .img2 { 471 | height: 40px; 472 | width: 40px; 473 | } 474 | html main #country-container #country-name, 475 | body main #country-container #country-name { 476 | font-size: 12px; 477 | } 478 | html main #country-container #no-results h1, 479 | body main #country-container #no-results h1 { 480 | font-size: 1.2rem; 481 | } 482 | html footer div, 483 | body footer div { 484 | padding: 1rem; 485 | font-size: 0.8rem; 486 | } 487 | html footer div #logo, 488 | body footer div #logo { 489 | width: 20px; 490 | } 491 | } 492 | @media only screen and (max-width: 746px) { 493 | html header div form #id_search_term, 494 | body header div form #id_search_term { 495 | width: 320px; 496 | } 497 | html header div form .search-dropdown, 498 | body header div form .search-dropdown { 499 | width: 340px; 500 | margin-left: -409px; 501 | } 502 | html main #country-container, 503 | body main #country-container { 504 | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 505 | } 506 | html main #country-container #description-card, 507 | body main #country-container #description-card { 508 | width: 508px; 509 | } 510 | } 511 | @media only screen and (max-width: 740px) { 512 | html header div form #id_search_term, 513 | body header div form #id_search_term { 514 | width: 320px; 515 | } 516 | html header div form .search-dropdown, 517 | body header div form .search-dropdown { 518 | width: 340px; 519 | margin-left: -409px; 520 | } 521 | html main #country-container, 522 | body main #country-container { 523 | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 524 | } 525 | html main #country-container #description-card, 526 | body main #country-container #description-card { 527 | width: 508px; 528 | } 529 | } 530 | @media only screen and (max-width: 712px) { 531 | html header div form #id_search_term, 532 | body header div form #id_search_term { 533 | width: 300px; 534 | margin-left: -15px; 535 | } 536 | html header div form .search-dropdown, 537 | body header div form .search-dropdown { 538 | width: 340px; 539 | margin-left: -388px; 540 | } 541 | html header div form #id_regions, 542 | body header div form #id_regions { 543 | margin-right: -15px; 544 | } 545 | html main #country-container #country-card, 546 | html main #country-container #description-card, 547 | html main #country-container .emblem-card, 548 | body main #country-container #country-card, 549 | body main #country-container #description-card, 550 | body main #country-container .emblem-card { 551 | font-size: 0.9rem; 552 | min-height: 360px; 553 | max-height: 360px; 554 | } 555 | html main #country-container #description-card, 556 | body main #country-container #description-card { 557 | width: 510px; 558 | } 559 | html main #country-container #img, 560 | body main #country-container #img { 561 | width: 130px; 562 | height: 80px; 563 | } 564 | html main #country-container .img2, 565 | body main #country-container .img2 { 566 | height: 35px; 567 | width: 35px; 568 | } 569 | } 570 | @media only screen and (max-width: 600px) { 571 | html header div form #id_regions, 572 | body header div form #id_regions { 573 | width: 100px; 574 | margin-left: -30px; 575 | } 576 | html main #country-container #country-card, 577 | html main #country-container #description-card, 578 | html main #country-container .emblem-card, 579 | body main #country-container #country-card, 580 | body main #country-container #description-card, 581 | body main #country-container .emblem-card { 582 | min-height: 340px; 583 | max-height: 340px; 584 | } 585 | } 586 | @media only screen and (max-width: 570px) { 587 | html header div form #id_search_term, 588 | body header div form #id_search_term { 589 | width: 280px; 590 | margin-right: 0; 591 | margin-left: -20px; 592 | } 593 | html header div form .search-dropdown, 594 | body header div form .search-dropdown { 595 | width: 340px; 596 | margin-left: -314px; 597 | } 598 | html header div form #id_regions, 599 | body header div form #id_regions { 600 | margin-right: -20px; 601 | } 602 | html main #country-container #description-card, 603 | body main #country-container #description-card { 604 | width: 536px; 605 | } 606 | } 607 | @media only screen and (max-width: 540px) { 608 | html header div form, 609 | body header div form { 610 | font-size: 15px; 611 | width: 60px; 612 | } 613 | html header div form #id_search_term, 614 | body header div form #id_search_term { 615 | height: 32px; 616 | width: 280px; 617 | margin-left: -15px; 618 | } 619 | html header div form .search-dropdown, 620 | body header div form .search-dropdown { 621 | width: 320px; 622 | margin-left: -258px; 623 | } 624 | html header div form #id_regions, 625 | body header div form #id_regions { 626 | margin-left: -20px; 627 | } 628 | html main #country-container #country-card, 629 | html main #country-container #description-card, 630 | html main #country-container .emblem-card, 631 | body main #country-container #country-card, 632 | body main #country-container #description-card, 633 | body main #country-container .emblem-card { 634 | font-size: 0.8rem; 635 | } 636 | html main #country-container #description-card, 637 | body main #country-container #description-card { 638 | width: 510px; 639 | } 640 | } 641 | @media only screen and (max-width: 534px) { 642 | html main #country-container #description-card, 643 | body main #country-container #description-card { 644 | width: 503px; 645 | } 646 | } 647 | @media only screen and (max-width: 480px) { 648 | html main #country-container, 649 | body main #country-container { 650 | padding: 0.5rem; 651 | grid-gap: 0.5rem; 652 | grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); 653 | } 654 | html main #country-container #country-card, 655 | html main #country-container #description-card, 656 | html main #country-container .emblem-card, 657 | body main #country-container #country-card, 658 | body main #country-container #description-card, 659 | body main #country-container .emblem-card { 660 | padding: 0.5rem; 661 | font-size: 0.7rem; 662 | min-height: 300px; 663 | max-height: 300px; 664 | } 665 | html main #country-container #country-card h1, 666 | body main #country-container #country-card h1 { 667 | font-size: 0.8rem; 668 | } 669 | html main #country-container #description-card, 670 | body main #country-container #description-card { 671 | width: 464px; 672 | } 673 | html main #country-container #description-card h1, 674 | body main #country-container #description-card h1 { 675 | font-size: 0.9rem; 676 | } 677 | html main #country-container #img, 678 | body main #country-container #img { 679 | width: 100px; 680 | height: 60px; 681 | } 682 | html main #country-container .img2, 683 | body main #country-container .img2 { 684 | height: 30px; 685 | width: 30px; 686 | } 687 | html main #country-container #country-name, 688 | body main #country-container #country-name { 689 | font-size: 10px; 690 | } 691 | html main #country-container #no-results h1, 692 | body main #country-container #no-results h1 { 693 | font-size: 1rem; 694 | } 695 | html footer div, 696 | body footer div { 697 | font-size: 0.7rem; 698 | } 699 | html footer div #logo, 700 | body footer div #logo { 701 | width: 15px; 702 | margin-left: 2rem; 703 | } 704 | html footer div #django, 705 | body footer div #django { 706 | width: 90px; 707 | } 708 | } 709 | @media only screen and (max-width: 432px) { 710 | html header div form, 711 | body header div form { 712 | font-size: 0.8rem; 713 | } 714 | html header div form #id_search_term, 715 | body header div form #id_search_term { 716 | height: 30px; 717 | width: 240px; 718 | margin-left: -25px; 719 | } 720 | html header div form .search-dropdown, 721 | body header div form .search-dropdown { 722 | width: 300px; 723 | margin-left: -231px; 724 | } 725 | html header div form #id_regions, 726 | body header div form #id_regions { 727 | width: 100px; 728 | margin-left: -30px; 729 | } 730 | html main #country-container #description-card, 731 | body main #country-container #description-card { 732 | width: 414px; 733 | } 734 | } 735 | @media only screen and (max-width: 416px) { 736 | html main #country-container #country-card, 737 | html main #country-container #description-card, 738 | html main #country-container .emblem-card, 739 | body main #country-container #country-card, 740 | body main #country-container #description-card, 741 | body main #country-container .emblem-card { 742 | font-size: 0.7rem; 743 | } 744 | html main #country-container #description-card, 745 | body main #country-container #description-card { 746 | width: 399px; 747 | } 748 | } 749 | @media only screen and (max-width: 412px) { 750 | html header div form, 751 | body header div form { 752 | font-size: 0.7rem; 753 | } 754 | html header div form #id_search_term, 755 | body header div form #id_search_term { 756 | height: 30px; 757 | width: 200px; 758 | margin-left: -25px; 759 | } 760 | html header div form .search-dropdown, 761 | body header div form .search-dropdown { 762 | width: 250px; 763 | margin-left: -209px; 764 | } 765 | html header div form #id_regions, 766 | body header div form #id_regions { 767 | width: 100px; 768 | margin-left: -30px; 769 | } 770 | html main #country-container, 771 | body main #country-container { 772 | padding: 0.5rem; 773 | grid-gap: 0.5rem; 774 | font-size: 0.6rem; 775 | grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); 776 | } 777 | html main #country-container #description-card, 778 | body main #country-container #description-card { 779 | width: 394px; 780 | } 781 | html main #country-container #img, 782 | body main #country-container #img { 783 | width: 100px; 784 | height: 60px; 785 | } 786 | html main #country-container .img2, 787 | body main #country-container .img2 { 788 | height: 30px; 789 | width: 30px; 790 | } 791 | } 792 | @media only screen and (max-width: 394px) { 793 | html main #country-container #description-card, 794 | body main #country-container #description-card { 795 | width: 378px; 796 | } 797 | } 798 | @media only screen and (max-width: 384px) { 799 | html header div form, 800 | body header div form { 801 | font-size: 0.8rem; 802 | } 803 | html header div form .search-dropdown, 804 | body header div form .search-dropdown { 805 | width: 280px; 806 | margin-left: -230px; 807 | } 808 | html main #country-container, 809 | body main #country-container { 810 | grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); 811 | } 812 | html main #country-container #country-card, 813 | html main #country-container #description-card, 814 | html main #country-container .emblem-card, 815 | body main #country-container #country-card, 816 | body main #country-container #description-card, 817 | body main #country-container .emblem-card { 818 | padding: 0.5rem; 819 | min-height: 275px; 820 | max-height: 275px; 821 | } 822 | html main #country-container #description-card, 823 | body main #country-container #description-card { 824 | width: 368px; 825 | } 826 | html main #country-container #img, 827 | body main #country-container #img { 828 | width: 100px; 829 | height: 60px; 830 | } 831 | html main #country-container .img2, 832 | body main #country-container .img2 { 833 | height: 30px; 834 | width: 30px; 835 | } 836 | } 837 | @media only screen and (max-width: 376px) { 838 | html main #country-container #description-card, 839 | body main #country-container #description-card { 840 | width: 359px; 841 | } 842 | } 843 | @media only screen and (max-width: 360px) { 844 | html main #country-container, 845 | body main #country-container { 846 | grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); 847 | } 848 | html main #country-container #description-card, 849 | body main #country-container #description-card { 850 | width: 346px; 851 | } 852 | } 853 | @media only screen and (max-width: 354px) { 854 | html main #country-container #description-card, 855 | body main #country-container #description-card { 856 | width: 338px; 857 | } 858 | } 859 | @media only screen and (max-width: 320px) { 860 | html header div form, 861 | body header div form { 862 | font-size: 0.6rem; 863 | } 864 | html header div form #id_search_term, 865 | body header div form #id_search_term { 866 | height: 30px; 867 | } 868 | html header div form .search-dropdown, 869 | body header div form .search-dropdown { 870 | width: 230px; 871 | margin-left: -186px; 872 | } 873 | html main #country-container, 874 | body main #country-container { 875 | grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); 876 | } 877 | html main #country-container #country-card, 878 | html main #country-container #description-card, 879 | html main #country-container .emblem-card, 880 | body main #country-container #country-card, 881 | body main #country-container #description-card, 882 | body main #country-container .emblem-card { 883 | font-size: 0.6rem; 884 | } 885 | html main #country-container #country-card h1, 886 | body main #country-container #country-card h1 { 887 | font-size: 0.7rem; 888 | } 889 | html main #country-container #description-card, 890 | body main #country-container #description-card { 891 | width: 302px; 892 | } 893 | html main #country-container #description-card h1, 894 | body main #country-container #description-card h1 { 895 | font-size: 0.8rem; 896 | } 897 | html main #country-container #img, 898 | body main #country-container #img { 899 | width: 80px; 900 | height: 50px; 901 | } 902 | html main #country-container .img2, 903 | body main #country-container .img2 { 904 | height: 20px; 905 | width: 20px; 906 | } 907 | html main #country-container #country-name, 908 | body main #country-container #country-name { 909 | font-size: 8px; 910 | } 911 | html main #country-container #no-results h1, 912 | body main #country-container #no-results h1 { 913 | font-size: 0.9rem; 914 | } 915 | html footer div, 916 | body footer div { 917 | height: 15vh; 918 | font-size: 0.5rem; 919 | } 920 | html footer div #logo, 921 | body footer div #logo { 922 | width: 10px; 923 | } 924 | html footer div #django, 925 | body footer div #django { 926 | width: 80px; 927 | } 928 | } 929 | @media only screen and (max-width: 280px) { 930 | html header div form, 931 | body header div form { 932 | font-size: 0.55rem; 933 | } 934 | html header div form #id_search_term, 935 | body header div form #id_search_term { 936 | width: 140px; 937 | } 938 | html header div form .search-dropdown, 939 | body header div form .search-dropdown { 940 | width: 200px; 941 | margin-left: -172px; 942 | } 943 | html header div form #id_regions, 944 | body header div form #id_regions { 945 | width: 85px; 946 | margin-left: -15px; 947 | } 948 | html main #country-container #country-card, 949 | html main #country-container #description-card, 950 | html main #country-container .emblem-card, 951 | body main #country-container #country-card, 952 | body main #country-container #description-card, 953 | body main #country-container .emblem-card { 954 | font-size: 0.55rem; 955 | min-height: 240px; 956 | max-height: 240px; 957 | } 958 | html main #country-container #description-card, 959 | body main #country-container #description-card { 960 | width: 262px; 961 | } 962 | } 963 | @media only screen and (max-width: 240px) { 964 | html header div form, 965 | body header div form { 966 | font-size: 0.45rem; 967 | } 968 | html header div form #id_search_term, 969 | body header div form #id_search_term { 970 | height: 30px; 971 | width: 120px; 972 | margin-left: -25px; 973 | } 974 | html header div form .search-dropdown, 975 | body header div form .search-dropdown { 976 | width: 180px; 977 | margin-left: -150px; 978 | } 979 | html header div form #id_regions, 980 | body header div form #id_regions { 981 | margin-left: -10px; 982 | } 983 | html main #country-container, 984 | body main #country-container { 985 | grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); 986 | } 987 | html main #country-container #country-card, 988 | html main #country-container #description-card, 989 | html main #country-container .emblem-card, 990 | body main #country-container #country-card, 991 | body main #country-container #description-card, 992 | body main #country-container .emblem-card { 993 | font-size: 0.45rem; 994 | } 995 | html main #country-container #country-card h1, 996 | body main #country-container #country-card h1 { 997 | font-size: 0.6rem; 998 | } 999 | html main #country-container #description-card, 1000 | body main #country-container #description-card { 1001 | width: 222px; 1002 | } 1003 | html main #country-container #description-card h1, 1004 | body main #country-container #description-card h1 { 1005 | font-size: 0.7rem; 1006 | } 1007 | html main #country-container #img, 1008 | body main #country-container #img { 1009 | width: 60px; 1010 | height: 40px; 1011 | } 1012 | html main #country-container .img2, 1013 | body main #country-container .img2 { 1014 | height: 15px; 1015 | width: 15px; 1016 | } 1017 | html main #country-container #country-name, 1018 | body main #country-container #country-name { 1019 | font-size: 6px; 1020 | } 1021 | html main #country-container #no-results h1, 1022 | body main #country-container #no-results h1 { 1023 | font-size: 0.7rem; 1024 | } 1025 | html footer div, 1026 | body footer div { 1027 | font-size: 0.4rem; 1028 | } 1029 | html footer div #logo, 1030 | body footer div #logo { 1031 | width: 9px; 1032 | } 1033 | html footer div #django, 1034 | body footer div #django { 1035 | width: 60px; 1036 | } 1037 | } 1038 | -------------------------------------------------------------------------------- /myproject/myapp/static/css/countries.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | html, 3 | body { 4 | margin: 0; 5 | padding: 0; 6 | background-color: #E5E4E2; 7 | -moz-box-sizing: border-box; 8 | -webkit-box-sizing: border-box; 9 | box-sizing: border-box; 10 | header { 11 | div { 12 | top: 0; 13 | left: 0; 14 | right: 0; 15 | height: 15vh; 16 | padding: 1.5rem 1rem; 17 | display: flex; 18 | color: white; 19 | position: fixed; 20 | white-space: nowrap; 21 | align-items: center; 22 | background-color: #393939; 23 | justify-content: space-between; 24 | font-family: Cambria, Cochin, Georgia, Times, "Times New Roman", serif; 25 | h1 { 26 | margin: 0; 27 | font-size: 1.37rem; 28 | text-shadow: 4px 4px black; 29 | font-weight: bold; 30 | font-family: American Trypewriter, sans-serif; 31 | } 32 | form { 33 | margin: 1rem; 34 | display: flex; 35 | align-items: center; 36 | } 37 | #id_search_term, 38 | #id_regions, 39 | #back-button, 40 | #back-button:after { 41 | -moz-border-radius: 10px; 42 | -webkit-border-radius: 10px; 43 | border-radius: 10px; 44 | } 45 | #id_search_term { 46 | width: 400px; 47 | height: 35px; 48 | padding: 10px; 49 | margin-right: 55px; 50 | } 51 | #search-glass { 52 | margin-left: 370px; 53 | border-radius: 50%; 54 | border: none; 55 | cursor: pointer; 56 | position: fixed; 57 | background: none; 58 | color: gray; 59 | } 60 | #search-glass:hover { 61 | color: #393939; 62 | } 63 | #id_regions { 64 | width: 120px; 65 | height: 30px; 66 | padding-left: 10px; 67 | background-color: white; 68 | } 69 | #back-button { 70 | width: 100px; 71 | height: 30px; 72 | font-size: 15px; 73 | margin-left: 50px; 74 | line-height: 15px; 75 | display: block; 76 | position: relative; 77 | transition: 0.3s; 78 | text-decoration: none; 79 | border: 2px solid #fff; 80 | box-shadow: 5px 5px black; 81 | background-color: #585858; 82 | } 83 | #back-button:active { 84 | transform: scale(0.98); 85 | box-shadow: 3px 2px 22px 1px rgba(0, 0, 0, 0.24); 86 | } 87 | #back-button:after { 88 | top: 0; 89 | right: 0; 90 | width: 0; 91 | height: 100%; 92 | line-height: 25px; 93 | color: #393939; 94 | content: "⟲"; 95 | position: absolute; 96 | transition: all 0.5s; 97 | background: ghostwhite; 98 | } 99 | #back-button:hover:after { 100 | width: 100%; 101 | } 102 | .search-dropdown { 103 | z-index: 1; 104 | top: 9.5vh; 105 | width: 400px; 106 | margin-left: -488px; 107 | border-radius: 10px; 108 | box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); 109 | display: none; 110 | border-top: none; 111 | position: absolute; 112 | list-style-type: none; 113 | border: 1px solid #ccc; 114 | background-color: #fff; 115 | } 116 | .search-item { 117 | padding: 8px 16px; 118 | color: black; 119 | cursor: pointer; 120 | } 121 | .search-item:hover { 122 | border-radius: 10px; 123 | background-color: #E5E4E2; 124 | } 125 | .active { 126 | border-radius: 10px; 127 | background-color: #E5E4E2; 128 | } 129 | } 130 | } 131 | main { 132 | #country-container { 133 | padding: 2rem; 134 | grid-gap: 2rem; 135 | margin-top: 15vh; 136 | min-height: 70vh; 137 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 138 | display: grid; 139 | align-items: flex-start; 140 | background-color: #E5E4E2; 141 | #country-card, 142 | #description-card, 143 | .emblem-card { 144 | padding: 1.5rem; 145 | font-size: 0.9rem; 146 | min-height: 444px; 147 | max-height: 444px; 148 | border-radius: 10px; 149 | box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2); 150 | display: flex; 151 | overflow-y: auto; 152 | overflow-x: hidden; 153 | text-align: inherit; 154 | align-items: center; 155 | flex-direction: column; 156 | background-color: white; 157 | font-family: Cambria, Cochin, Georgia, Times, "Times New Roman", serif; 158 | } 159 | #country-card { 160 | h1 { 161 | margin-top: 15px; 162 | font-size: 1rem; 163 | text-align: center; 164 | font-weight: bold; 165 | } 166 | } 167 | #description-card { 168 | font-size: 1rem; 169 | line-height: 1.2; 170 | h1 { 171 | margin-top: 15px; 172 | font-size: 1.1rem; 173 | font-weight: bold; 174 | } 175 | } 176 | .emblem-card { 177 | pointer-events: none; 178 | justify-content: center; 179 | } 180 | #country-card:hover { 181 | cursor: pointer; 182 | background-color: ghostwhite; 183 | } 184 | #country-card.searched:hover { 185 | cursor: default; 186 | background-color: white; 187 | } 188 | #img { 189 | width: 225px; 190 | height: 135px; 191 | border-radius: 10px; 192 | border-style: ridge; 193 | pointer-events: none; 194 | border-color: #C0C0C0; 195 | } 196 | .img2 { 197 | height: 60px; 198 | width: 60px; 199 | border-radius: 50%; 200 | object-fit: contain; 201 | border-style: ridge; 202 | border-color: #C0C0C0; 203 | background-color: #E5E4E2; 204 | } 205 | .img3 { 206 | min-width: 100%; 207 | min-height: 100%; 208 | aspect-ratio: 0.5; 209 | object-fit: contain; 210 | } 211 | #country-name { 212 | font-size: 15px; 213 | } 214 | #no-results h1 { 215 | margin-top: 0; 216 | font-size: 1.5rem; 217 | font-weight: bold; 218 | white-space: nowrap; 219 | } 220 | } 221 | } 222 | footer { 223 | div { 224 | height: 15vh; 225 | padding: 1.5rem 1rem; 226 | text-shadow: 2px 2px black; 227 | color: white; 228 | display: flex; 229 | align-items: center; 230 | white-space: nowrap; 231 | background-color: #393939; 232 | justify-content: space-between; 233 | font-family: Cambria, Cochin, Georgia, Times, "Times New Roman", serif; 234 | a, 235 | a:link, 236 | a:visited, 237 | a:active { 238 | color: white; 239 | font-weight: bold; 240 | text-decoration: none; 241 | } 242 | a, 243 | img { 244 | opacity: 0.9; 245 | } 246 | a:hover, 247 | img:hover { 248 | opacity: 1; 249 | } 250 | p { 251 | margin-top: 1rem; 252 | } 253 | #logo { 254 | width: 25px; 255 | margin-right: 2rem; 256 | } 257 | } 258 | } 259 | } 260 | @media only screen and (min-width: 1025px) and (max-width: 1368px) { 261 | html, 262 | body { 263 | header { 264 | div { 265 | .search-dropdown { 266 | margin-left: -487px; 267 | } 268 | } 269 | } 270 | main { 271 | #country-container { 272 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 273 | } 274 | } 275 | } 276 | } 277 | @media only screen and (max-width: 1024px) { 278 | html, 279 | body { 280 | header { 281 | div { 282 | h1 { 283 | font-size: 1.2rem; 284 | } 285 | form { 286 | #id_search_term { 287 | margin-left: 100px; 288 | } 289 | #search-glass { 290 | margin-left: 470px; 291 | } 292 | #id_regions { 293 | width: 110px; 294 | margin-right: 150px; 295 | } 296 | } 297 | } 298 | } 299 | main { 300 | #country-container { 301 | padding: 1.5rem; 302 | grid-gap: 1.5rem; 303 | #country-card, 304 | #description-card, 305 | .emblem-card { 306 | min-height: 430px; 307 | max-height: 430px; 308 | } 309 | #country-card { 310 | h1 { 311 | font-size: 1rem; 312 | } 313 | } 314 | #description-card { 315 | width: 912px; 316 | h1 { 317 | font-size: 1.1rem; 318 | } 319 | } 320 | #img { 321 | width: 200px; 322 | height: 120px; 323 | } 324 | .img2 { 325 | height: 50px; 326 | width: 50px; 327 | } 328 | #country-name { 329 | font-size: 15px; 330 | } 331 | #no-results h1 { 332 | font-size: 1.5rem; 333 | } 334 | } 335 | } 336 | footer { 337 | div { 338 | padding: 1.5rem; 339 | font-size: 0.9rem; 340 | #logo { 341 | width: 25px; 342 | } 343 | } 344 | } 345 | } 346 | } 347 | @media only screen and (max-width: 993px) { 348 | html, 349 | body { 350 | header { 351 | div { 352 | h1 { 353 | display: none; 354 | } 355 | form { 356 | #id_search_term { 357 | width: 340px; 358 | margin-left: 0; 359 | } 360 | .search-dropdown { 361 | width: 340px; 362 | margin-left: -428px; 363 | } 364 | #search-glass { 365 | display: none; 366 | } 367 | #id_regions { 368 | margin-right: 0; 369 | } 370 | } 371 | } 372 | } 373 | main { 374 | #country-container { 375 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 376 | #description-card { 377 | width: 672px; 378 | } 379 | } 380 | } 381 | } 382 | } 383 | @media only screen and (min-width: 992px) and (max-width: 993px) { 384 | html, 385 | body { 386 | main { 387 | #country-container { 388 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 389 | #description-card { 390 | width: 912px; 391 | } 392 | } 393 | } 394 | } 395 | } 396 | @media only screen and (max-width: 769px) { 397 | html, 398 | body { 399 | header { 400 | div { 401 | form { 402 | font-size: 1rem; 403 | #id_search_term { 404 | width: 340px; 405 | } 406 | #search-glass { 407 | display: none; 408 | } 409 | } 410 | } 411 | } 412 | main { 413 | #country-container { 414 | padding: 1rem; 415 | grid-gap: 1rem; 416 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 417 | #country-card, 418 | #description-card, 419 | .emblem-card { 420 | padding: 1rem; 421 | font-size: 0.9rem; 422 | min-height: 370px; 423 | max-height: 370px; 424 | } 425 | #country-card { 426 | h1 { 427 | font-size: 0.9rem; 428 | } 429 | } 430 | #description-card { 431 | width: 690px; 432 | h1 { 433 | font-size: 1rem; 434 | } 435 | } 436 | #img { 437 | width: 150px; 438 | height: 90px; 439 | } 440 | .img2 { 441 | height: 40px; 442 | width: 40px; 443 | } 444 | #country-name { 445 | font-size: 12px; 446 | } 447 | #no-results h1 { 448 | font-size: 1.2rem; 449 | } 450 | } 451 | } 452 | footer { 453 | div { 454 | padding: 1rem; 455 | font-size: 0.8rem; 456 | #logo { 457 | width: 20px; 458 | } 459 | } 460 | } 461 | } 462 | } 463 | @media only screen and (max-width: 746px) { 464 | html, 465 | body { 466 | header { 467 | div { 468 | form { 469 | #id_search_term { 470 | width: 320px; 471 | } 472 | .search-dropdown { 473 | width: 340px; 474 | margin-left: -409px; 475 | } 476 | } 477 | } 478 | } 479 | main { 480 | #country-container { 481 | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 482 | #description-card { 483 | width: 508px; 484 | } 485 | } 486 | } 487 | } 488 | } 489 | @media only screen and (max-width: 740px) { 490 | html, 491 | body { 492 | header { 493 | div { 494 | form { 495 | #id_search_term { 496 | width: 320px; 497 | } 498 | .search-dropdown { 499 | width: 340px; 500 | margin-left: -409px; 501 | } 502 | } 503 | } 504 | } 505 | main { 506 | #country-container { 507 | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 508 | #description-card { 509 | width: 508px; 510 | } 511 | } 512 | } 513 | } 514 | } 515 | @media only screen and (max-width: 712px) { 516 | html, 517 | body { 518 | header { 519 | div { 520 | form { 521 | #id_search_term { 522 | width: 300px; 523 | margin-left: -15px; 524 | } 525 | .search-dropdown { 526 | width: 340px; 527 | margin-left: -388px; 528 | } 529 | #id_regions { 530 | margin-right: -15px; 531 | } 532 | } 533 | } 534 | } 535 | main { 536 | #country-container { 537 | #country-card, 538 | #description-card, 539 | .emblem-card { 540 | font-size: 0.9rem; 541 | min-height: 360px; 542 | max-height: 360px; 543 | } 544 | #description-card { 545 | width: 510px; 546 | } 547 | #img { 548 | width: 130px; 549 | height: 80px; 550 | } 551 | .img2 { 552 | height: 35px; 553 | width: 35px; 554 | } 555 | } 556 | } 557 | } 558 | } 559 | @media only screen and (max-width: 600px) { 560 | html, 561 | body { 562 | header { 563 | div { 564 | form { 565 | #id_regions { 566 | width: 100px; 567 | margin-left: -30px; 568 | } 569 | } 570 | } 571 | } 572 | main { 573 | #country-container { 574 | #country-card, 575 | #description-card, 576 | .emblem-card { 577 | min-height: 340px; 578 | max-height: 340px; 579 | } 580 | } 581 | } 582 | } 583 | } 584 | @media only screen and (max-width: 570px) { 585 | html, 586 | body { 587 | header { 588 | div { 589 | form { 590 | #id_search_term { 591 | width: 280px; 592 | margin-right: 0; 593 | margin-left: -20px; 594 | } 595 | .search-dropdown { 596 | width: 340px; 597 | margin-left: -314px; 598 | } 599 | #id_regions { 600 | margin-right: -20px; 601 | } 602 | } 603 | } 604 | } 605 | main { 606 | #country-container { 607 | #description-card { 608 | width: 536px; 609 | } 610 | } 611 | } 612 | } 613 | } 614 | @media only screen and (max-width: 540px) { 615 | html, 616 | body { 617 | header { 618 | div { 619 | form { 620 | font-size: 15px; 621 | width: 60px; 622 | #id_search_term { 623 | height: 32px; 624 | width: 280px; 625 | margin-left: -15px; 626 | } 627 | .search-dropdown { 628 | width: 320px; 629 | margin-left: -258px; 630 | } 631 | #id_regions { 632 | margin-left: -20px; 633 | } 634 | } 635 | } 636 | } 637 | main { 638 | #country-container { 639 | #country-card, 640 | #description-card, 641 | .emblem-card { 642 | font-size: 0.8rem; 643 | } 644 | #description-card { 645 | width: 510px; 646 | } 647 | } 648 | } 649 | } 650 | } 651 | @media only screen and (max-width: 534px) { 652 | html, 653 | body { 654 | main { 655 | #country-container { 656 | #description-card { 657 | width: 503px; 658 | } 659 | } 660 | } 661 | } 662 | } 663 | @media only screen and (max-width: 480px) { 664 | html, 665 | body { 666 | main { 667 | #country-container { 668 | padding: 0.5rem; 669 | grid-gap: 0.5rem; 670 | grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); 671 | #country-card, 672 | #description-card, 673 | .emblem-card { 674 | padding: 0.5rem; 675 | font-size: 0.7rem; 676 | min-height: 300px; 677 | max-height: 300px; 678 | } 679 | #country-card { 680 | h1 { 681 | font-size: 0.8rem; 682 | } 683 | } 684 | #description-card { 685 | width: 464px; 686 | h1 { 687 | font-size: 0.9rem; 688 | } 689 | } 690 | #img { 691 | width: 100px; 692 | height: 60px; 693 | } 694 | .img2 { 695 | height: 30px; 696 | width: 30px; 697 | } 698 | #country-name { 699 | font-size: 10px; 700 | } 701 | #no-results h1 { 702 | font-size: 1rem; 703 | } 704 | } 705 | } 706 | footer { 707 | div { 708 | font-size: 0.7rem; 709 | #logo { 710 | width: 15px; 711 | margin-left: 2rem; 712 | } 713 | #django { 714 | width: 90px; 715 | } 716 | } 717 | } 718 | } 719 | } 720 | @media only screen and (max-width: 432px) { 721 | html, 722 | body { 723 | header { 724 | div { 725 | form { 726 | font-size: 0.8rem; 727 | #id_search_term { 728 | height: 30px; 729 | width: 240px; 730 | margin-left: -25px; 731 | } 732 | .search-dropdown { 733 | width: 300px; 734 | margin-left: -231px; 735 | } 736 | #id_regions { 737 | width: 100px; 738 | margin-left: -30px; 739 | } 740 | } 741 | } 742 | } 743 | main { 744 | #country-container { 745 | #description-card { 746 | width: 414px; 747 | } 748 | } 749 | } 750 | } 751 | } 752 | @media only screen and (max-width: 416px) { 753 | html, 754 | body { 755 | main { 756 | #country-container { 757 | #country-card, 758 | #description-card, 759 | .emblem-card { 760 | font-size: 0.7rem; 761 | } 762 | #description-card { 763 | width: 399px; 764 | } 765 | } 766 | } 767 | } 768 | } 769 | @media only screen and (max-width: 412px) { 770 | html, 771 | body { 772 | header { 773 | div { 774 | form { 775 | font-size: 0.7rem; 776 | #id_search_term { 777 | height: 30px; 778 | width: 200px; 779 | margin-left: -25px; 780 | } 781 | .search-dropdown { 782 | width: 250px; 783 | margin-left: -209px; 784 | } 785 | #id_regions { 786 | width: 100px; 787 | margin-left: -30px; 788 | } 789 | } 790 | } 791 | } 792 | main { 793 | #country-container { 794 | padding: 0.5rem; 795 | grid-gap: 0.5rem; 796 | font-size: 0.6rem; 797 | grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); 798 | #description-card { 799 | width: 394px; 800 | } 801 | #img { 802 | width: 100px; 803 | height: 60px; 804 | } 805 | .img2 { 806 | height: 30px; 807 | width: 30px; 808 | } 809 | } 810 | } 811 | } 812 | } 813 | @media only screen and (max-width: 394px) { 814 | html, 815 | body { 816 | main { 817 | #country-container { 818 | #description-card { 819 | width: 378px; 820 | } 821 | } 822 | } 823 | } 824 | } 825 | @media only screen and (max-width: 384px) { 826 | html, 827 | body { 828 | header { 829 | div { 830 | form { 831 | font-size: 0.8rem; 832 | .search-dropdown { 833 | width: 280px; 834 | margin-left: -230px; 835 | } 836 | } 837 | } 838 | } 839 | main { 840 | #country-container { 841 | grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); 842 | #country-card, 843 | #description-card, 844 | .emblem-card { 845 | padding: 0.5rem; 846 | min-height: 275px; 847 | max-height: 275px; 848 | } 849 | #description-card { 850 | width: 368px; 851 | } 852 | #img { 853 | width: 100px; 854 | height: 60px; 855 | } 856 | .img2 { 857 | height: 30px; 858 | width: 30px; 859 | } 860 | } 861 | } 862 | } 863 | } 864 | @media only screen and (max-width: 376px) { 865 | html, 866 | body { 867 | main { 868 | #country-container { 869 | #description-card { 870 | width: 359px; 871 | } 872 | } 873 | } 874 | } 875 | } 876 | @media only screen and (max-width: 360px) { 877 | html, 878 | body { 879 | main { 880 | #country-container { 881 | grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); 882 | #description-card { 883 | width: 346px; 884 | } 885 | } 886 | } 887 | } 888 | } 889 | @media only screen and (max-width: 354px) { 890 | html, 891 | body { 892 | main { 893 | #country-container { 894 | #description-card { 895 | width: 338px; 896 | } 897 | } 898 | } 899 | } 900 | } 901 | @media only screen and (max-width: 320px) { 902 | html, 903 | body { 904 | header { 905 | div { 906 | form { 907 | font-size: 0.6rem; 908 | #id_search_term { 909 | height: 30px; 910 | } 911 | .search-dropdown { 912 | width: 230px; 913 | margin-left: -186px; 914 | } 915 | } 916 | } 917 | } 918 | main { 919 | #country-container { 920 | grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); 921 | #country-card, 922 | #description-card, 923 | .emblem-card { 924 | font-size: 0.6rem; 925 | } 926 | #country-card { 927 | h1 { 928 | font-size: 0.7rem; 929 | } 930 | } 931 | #description-card { 932 | width: 302px; 933 | h1 { 934 | font-size: 0.8rem; 935 | } 936 | } 937 | #img { 938 | width: 80px; 939 | height: 50px; 940 | } 941 | .img2 { 942 | height: 20px; 943 | width: 20px; 944 | } 945 | #country-name { 946 | font-size: 8px; 947 | } 948 | #no-results h1 { 949 | font-size: 0.9rem; 950 | } 951 | } 952 | } 953 | footer { 954 | div { 955 | height: 15vh; 956 | font-size: 0.5rem; 957 | #logo { 958 | width: 10px; 959 | } 960 | #django { 961 | width: 80px; 962 | } 963 | } 964 | } 965 | } 966 | } 967 | @media only screen and (max-width: 280px) { 968 | html, 969 | body { 970 | header { 971 | div { 972 | form { 973 | font-size: 0.55rem; 974 | #id_search_term { 975 | width: 140px; 976 | } 977 | .search-dropdown { 978 | width: 200px; 979 | margin-left: -172px; 980 | } 981 | #id_regions { 982 | width: 85px; 983 | margin-left: -15px; 984 | } 985 | } 986 | } 987 | } 988 | main { 989 | #country-container { 990 | #country-card, 991 | #description-card, 992 | .emblem-card { 993 | font-size: 0.55rem; 994 | min-height: 240px; 995 | max-height: 240px; 996 | } 997 | #description-card { 998 | width: 262px; 999 | } 1000 | } 1001 | } 1002 | } 1003 | } 1004 | @media only screen and (max-width: 240px) { 1005 | html, 1006 | body { 1007 | header { 1008 | div { 1009 | form { 1010 | font-size: 0.45rem; 1011 | #id_search_term { 1012 | height: 30px; 1013 | width: 120px; 1014 | margin-left: -25px; 1015 | } 1016 | .search-dropdown { 1017 | width: 180px; 1018 | margin-left: -150px; 1019 | } 1020 | #id_regions { 1021 | margin-left: -10px; 1022 | } 1023 | } 1024 | } 1025 | } 1026 | main { 1027 | #country-container { 1028 | grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); 1029 | #country-card, 1030 | #description-card, 1031 | .emblem-card { 1032 | font-size: 0.45rem; 1033 | } 1034 | #country-card { 1035 | h1 { 1036 | font-size: 0.6rem; 1037 | } 1038 | } 1039 | #description-card { 1040 | width: 222px; 1041 | h1 { 1042 | font-size: 0.7rem; 1043 | } 1044 | } 1045 | #img { 1046 | width: 60px; 1047 | height: 40px; 1048 | } 1049 | .img2 { 1050 | height: 15px; 1051 | width: 15px; 1052 | } 1053 | #country-name { 1054 | font-size: 6px; 1055 | } 1056 | #no-results h1 { 1057 | font-size: 0.7rem; 1058 | } 1059 | } 1060 | } 1061 | footer { 1062 | div { 1063 | font-size: 0.4rem; 1064 | #logo { 1065 | width: 9px; 1066 | } 1067 | #django { 1068 | width: 60px; 1069 | } 1070 | } 1071 | } 1072 | } 1073 | } 1074 | -------------------------------------------------------------------------------- /myproject/myapp/static/css/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | background-color: #E5E4E2; 7 | -moz-box-sizing: border-box; 8 | -webkit-box-sizing: border-box; 9 | 10 | main { 11 | div { 12 | padding: 10rem; 13 | text-align: center; 14 | 15 | h1 { 16 | font-size: 2rem; 17 | margin-top: 15px; 18 | font-weight: bold; 19 | color: ghostwhite; 20 | text-shadow: 2px 0 #000000, -2px 0 #000000, 0 2px #000000, 0 -2px #000000, 21 | 1px 1px #000000, -1px -1px #000000, 1px -1px #000000, -1px 1px #000000; 22 | font-family: Comic Sans MS, cursive, sans-serif; 23 | } 24 | 25 | h2 { 26 | margin-top: 3rem; 27 | font-size: 1.1rem; 28 | font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif; 29 | } 30 | 31 | button { 32 | height: 40px; 33 | padding: 1rem; 34 | line-height: 0; 35 | font-size: 1rem; 36 | transition: 0.3s; 37 | border-radius: 10px; 38 | color: white; 39 | cursor: pointer; 40 | font-weight: bold; 41 | display: inline-block; 42 | background-color: #393939; 43 | border: 2px solid #c1c1c1; 44 | box-shadow: 5px 5px black; 45 | font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif; 46 | } 47 | 48 | button:hover { 49 | background-color: #585858; 50 | } 51 | 52 | button:active { 53 | transform: scale(0.98); 54 | box-shadow: 3px 2px 22px 1px rgba(0, 0, 0, 0.24); 55 | } 56 | 57 | p { 58 | font-size: 1rem; 59 | margin-top: 3rem; 60 | font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif; 61 | } 62 | } 63 | } 64 | 65 | /* Large Devices, Wide Screens */ 66 | @media only screen and (max-width: 1200px) { 67 | main { 68 | div { 69 | padding: 8rem; 70 | 71 | h1 { 72 | font-size: 1.9rem; 73 | } 74 | 75 | h2 { 76 | font-size: 1.1rem; 77 | } 78 | 79 | button { 80 | height: 35px; 81 | padding: 0.5rem; 82 | font-size: 1rem; 83 | } 84 | 85 | p { 86 | font-size: 1rem; 87 | } 88 | } 89 | } 90 | } 91 | 92 | /* Large tablets */ 93 | @media only screen and (max-width: 900px) { 94 | main { 95 | div { 96 | padding: 7rem; 97 | 98 | h1 { 99 | font-size: 1.8rem; 100 | } 101 | 102 | h2 { 103 | font-size: 1rem; 104 | } 105 | 106 | button { 107 | height: 35px; 108 | padding: 0.5rem; 109 | font-size: 0.9rem; 110 | } 111 | 112 | p { 113 | font-size: 0.9rem; 114 | } 115 | } 116 | } 117 | } 118 | 119 | /* Mid-size tablets */ 120 | @media only screen and (max-width: 768px) { 121 | main { 122 | div { 123 | padding: 5rem; 124 | 125 | h1 { 126 | font-size: 1.5rem; 127 | } 128 | 129 | h2 { 130 | font-size: 1rem; 131 | } 132 | 133 | button { 134 | height: 30px; 135 | padding: 0.5rem; 136 | font-size: 0.8rem; 137 | } 138 | 139 | p { 140 | font-size: 0.8rem; 141 | } 142 | } 143 | } 144 | } 145 | 146 | /* Small tablets */ 147 | @media only screen and (max-width: 600px) { 148 | main { 149 | div { 150 | padding: 3rem; 151 | 152 | h1 { 153 | font-size: 1.3rem; 154 | } 155 | 156 | h2 { 157 | font-size: 0.9rem; 158 | } 159 | 160 | button { 161 | height: 25px; 162 | padding: 0.5rem; 163 | font-size: 0.7rem; 164 | } 165 | 166 | p { 167 | font-size: 0.7rem; 168 | } 169 | } 170 | } 171 | } 172 | 173 | /* Small Devices, Tablets */ 174 | @media only screen and (max-width: 480px) { 175 | main { 176 | div { 177 | padding: 2rem; 178 | 179 | h1 { 180 | font-size: 1.2rem; 181 | } 182 | 183 | h2 { 184 | font-size: 0.8rem; 185 | } 186 | 187 | button { 188 | height: 25px; 189 | padding: 0.5rem; 190 | font-size: 0.7rem; 191 | } 192 | 193 | p { 194 | font-size: 0.7rem; 195 | } 196 | } 197 | } 198 | } 199 | 200 | /* Mobile Phones */ 201 | @media only screen and (max-width: 320px) { 202 | main { 203 | div { 204 | padding: 1rem; 205 | 206 | h1 { 207 | font-size: 1rem; 208 | } 209 | 210 | h2 { 211 | font-size: 0.7rem; 212 | } 213 | 214 | button { 215 | height: 20px; 216 | padding: 0.5rem; 217 | font-size: 0.6rem; 218 | } 219 | 220 | p { 221 | font-size: 0.6rem; 222 | } 223 | } 224 | } 225 | } 226 | } -------------------------------------------------------------------------------- /myproject/myapp/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davevad93/rest-countries-django-app/2f49ffbab8135c732a7a54a1cefd3aa265aa3711/myproject/myapp/static/img/favicon.ico -------------------------------------------------------------------------------- /myproject/myapp/static/img/github-mark-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davevad93/rest-countries-django-app/2f49ffbab8135c732a7a54a1cefd3aa265aa3711/myproject/myapp/static/img/github-mark-white.png -------------------------------------------------------------------------------- /myproject/myapp/static/js/countries.js: -------------------------------------------------------------------------------- 1 | // Class to manage search input, suggestions dropdown, and country cards. 2 | class CountrySearch { 3 | constructor() { 4 | this.searchInput = document.querySelector("#id_search_term"); // Search input field 5 | this.searchDropdown = document.querySelector("#search-dropdown"); // Suggestions dropdown 6 | this.activeSearchItem = -1; // Index of active search suggestion 7 | this.init(); // Initialize functionality 8 | } 9 | 10 | // Initialize search input and dropdown 11 | init() { 12 | window.onload = () => { 13 | this.handleSearchTerm(); // Handle search term 14 | this.searchInput.addEventListener("input", this.handleSearchInputChange.bind(this)); // Listen for input changes 15 | this.searchInput.addEventListener("keydown", this.handleSearchInputKeyDown.bind(this)); // Listen for keydown events 16 | }; 17 | this.attachCardEventListeners(); // Attach event listeners to country cards 18 | } 19 | 20 | // Handle keydown event on search input 21 | handleSearchInputKeyDown(event) { 22 | const items = Array.from(this.searchDropdown.children); // Convert dropdown children to array 23 | if (items.length === 0) return; // Return if no suggestions 24 | 25 | if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { 26 | this.handleArrowKeys(event, items); // Handle arrow key navigation 27 | } else if (event.key === 'Enter') { 28 | this.handleEnterKey(items); // Handle enter key selection 29 | } 30 | } 31 | 32 | // Handle arrow key press for suggestion selection 33 | handleArrowKeys(event, items) { 34 | event.preventDefault(); // Prevent default behavior 35 | this.removeActiveClass(items); // Remove active class from suggestions 36 | this.activeSearchItem = event.key === 'ArrowDown' 37 | ? (this.activeSearchItem + 1) % items.length // Move down 38 | : (this.activeSearchItem - 1 + items.length) % items.length; // Move up 39 | 40 | items[this.activeSearchItem].classList.add('active'); // Add active class to selected suggestion 41 | } 42 | 43 | // Handle enter key press for suggestion selection 44 | handleEnterKey(items) { 45 | if (this.activeSearchItem === -1) return; // Return if no active suggestion 46 | const activeItem = items[this.activeSearchItem]; // Get active suggestion 47 | this.searchInput.value = activeItem.textContent; // Set input value to suggestion 48 | this.clearSearchDropdown(); // Clear suggestions dropdown 49 | this.redirectToSearch(activeItem.textContent); // Redirect to search page 50 | this.handleSearchTerm(activeItem.textContent); // Handle search term 51 | this.activeSearchItem = -1; // Reset active suggestion 52 | } 53 | 54 | // Handle search term on page load or input change 55 | handleSearchTerm(searchTerm = null) { 56 | if (!searchTerm) { 57 | const urlParams = new URLSearchParams(window.location.search); // Get URL parameters 58 | searchTerm = urlParams.get('search_term'); // Get search term from URL 59 | } 60 | 61 | if (searchTerm) { 62 | this.handleSearchedCountry(searchTerm); // Handle searched country 63 | } 64 | if (!searchTerm) this.handleSearchInputChange(); // Handle input change if no search term 65 | } 66 | 67 | // Handle search input change event 68 | handleSearchInputChange() { 69 | const searchValue = this.searchInput.value.trim(); // Get trimmed input value 70 | searchValue ? this.fetchSearchSuggestions(searchValue) : this.clearSearchDropdown(); // Fetch suggestions or clear dropdown 71 | } 72 | 73 | // Fetch search suggestions from server 74 | fetchSearchSuggestions(searchValue) { 75 | fetch(`/search_countries/?search_term=${encodeURIComponent(searchValue)}&autocomplete=true`) 76 | .then((response) => response.json()) 77 | .then((data) => this.updateSuggestionsDropdown(data.suggestions)); // Update dropdown with suggestions 78 | } 79 | 80 | // Update dropdown with search suggestions 81 | updateSuggestionsDropdown(suggestions) { 82 | this.clearSearchDropdown(); // Clear dropdown 83 | suggestions.forEach((suggestion) => { 84 | const listItem = this.createListItem(suggestion); // Create list item for suggestion 85 | this.searchDropdown.appendChild(listItem); // Append suggestion to dropdown 86 | }); 87 | this.searchDropdown.style.display = 'block'; // Display dropdown 88 | } 89 | 90 | // Create list item for search dropdown 91 | createListItem(suggestion) { 92 | const listItem = document.createElement('li'); // Create list item 93 | listItem.textContent = suggestion; // Set text content 94 | listItem.classList.add('search-item'); // Add class 95 | listItem.addEventListener('click', () => this.redirectToSearch(suggestion)); // Add click event listener 96 | return listItem; 97 | } 98 | 99 | // Clear dropdown on click outside 100 | clearDropdownOnClick() { 101 | document.addEventListener('click', (event) => { 102 | const isClickInsideDropdown = this.searchDropdown.contains(event.target); // Check if click is inside dropdown 103 | if (!isClickInsideDropdown) { 104 | this.searchDropdown.innerHTML = ''; // Clear dropdown 105 | this.searchDropdown.style.display = 'none'; // Hide dropdown 106 | } 107 | }); 108 | } 109 | 110 | // Clear search dropdown 111 | clearSearchDropdown() { 112 | this.searchDropdown.innerHTML = ''; // Clear dropdown 113 | this.searchDropdown.style.display = 'none'; // Hide dropdown 114 | this.clearDropdownOnClick(); // Clear dropdown on click outside 115 | } 116 | 117 | // Attach event listeners to country cards 118 | attachCardEventListeners() { 119 | // Check page location to avoid attaching listeners on search page 120 | if (window.location.pathname !== '/search_countries/') { 121 | const countryCards = document.querySelectorAll("#country-card"); // Get country cards 122 | countryCards.forEach((countryCard) => { 123 | countryCard.addEventListener('click', () => { 124 | const countryName = countryCard.querySelector('#country-name').textContent; // Get country name 125 | if (this.searchInput.value.trim() !== countryName) { 126 | this.redirectToSearch(countryName); // Redirect to search if country name doesn't match input 127 | } 128 | }); 129 | }); 130 | } 131 | } 132 | 133 | // Handle searched country and disable pointer events on page load 134 | handleSearchedCountry(searchTerm) { 135 | const countryCards = document.querySelectorAll('#country-card'); // Get country cards 136 | countryCards.forEach((card) => { 137 | const countryName = card.querySelector('#country-name').textContent; // Get country name 138 | if (countryName.toLowerCase() === searchTerm.toLowerCase()) { 139 | card.classList.add('searched'); // Add class to indicate searched country 140 | } 141 | }); 142 | } 143 | 144 | // Remove active class from search suggestion items 145 | removeActiveClass(items) { 146 | if (this.activeSearchItem >= 0 && this.activeSearchItem < items.length) { 147 | items[this.activeSearchItem].classList.remove('active'); // Remove active class 148 | } 149 | } 150 | 151 | // Redirect to search page 152 | redirectToSearch(searchTerm) { 153 | window.location.href = `/search_countries/?search_term=${encodeURIComponent(searchTerm)}`; // Redirect with search term 154 | } 155 | } 156 | 157 | // Class to manage region select form 158 | class RegionSelect { 159 | constructor(countrySearch) { 160 | this.regionSelect = document.querySelector("#id_regions"); // Region select element 161 | this.countryContainer = document.querySelector("#country-container"); // Country container 162 | this.countrySearch = countrySearch; // CountrySearch instance 163 | if (this.regionSelect && this.countryContainer) { 164 | this.init(); // Initialize functionality 165 | } 166 | } 167 | 168 | // Initialize the region select. 169 | init() { 170 | if (this.regionSelect) { 171 | this.regionSelect.addEventListener("change", () => this.handleRegionSelectChange()); // Add event listener for 'change' event to handle region selection changes 172 | } 173 | } 174 | 175 | // Fetch countries by region from the server 176 | fetchCountriesByRegion(region) { 177 | this.countryContainer.innerHTML = ''; // Clear country container 178 | fetch(`/filter_regions/?region=${region}`) 179 | .then((response) => response.json()) 180 | .then((data) => { 181 | this.countryContainer.innerHTML = data.data; // Update country container with fetched data 182 | this.countrySearch.attachCardEventListeners(); // Attach event listeners to updated country cards 183 | }); 184 | } 185 | 186 | // Handle region select change event 187 | handleRegionSelectChange() { 188 | const selectedRegion = this.regionSelect.value; // Get selected region 189 | this.fetchCountriesByRegion(selectedRegion); // Fetch countries for selected region 190 | } 191 | } 192 | 193 | // Initialize the CountrySearch and RegionSelect classes when DOM content is loaded 194 | document.addEventListener("DOMContentLoaded", function () { 195 | const countrySearch = new CountrySearch(); // Initialize CountrySearch 196 | if (countrySearch) { 197 | return new RegionSelect(countrySearch); // Initialize RegionSelect with CountrySearch instance 198 | } 199 | }); -------------------------------------------------------------------------------- /myproject/myapp/static/json/emblems.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "country": { 4 | "name": "American Samoa", 5 | "url": "https://upload.wikimedia.org/wikipedia/commons/d/df/Seal_of_American_Samoa.svg", 6 | "license": "Public domain" 7 | } 8 | }, 9 | { 10 | "country": { 11 | "name": "Angola", 12 | "url": "https://upload.wikimedia.org/wikipedia/commons/f/ff/Emblem_of_Angola.svg", 13 | "license": "Public domain" 14 | } 15 | }, 16 | { 17 | "country": { 18 | "name": "British Indian Ocean Territory", 19 | "url": "https://upload.wikimedia.org/wikipedia/commons/a/a5/Coat_of_arms_of_the_British_Indian_Ocean_Territory.svg", 20 | "license": "Demidow, Coat of arms of the British Indian Ocean Territory, CC BY-SA 3.0" 21 | } 22 | }, 23 | { 24 | "country": { 25 | "name": "British Virgin Islands", 26 | "url": "https://upload.wikimedia.org/wikipedia/commons/0/05/Coat_of_arms_of_the_British_Virgin_Islands.svg", 27 | "license": "Public domain" 28 | } 29 | }, 30 | { 31 | "country": { 32 | "name": "Cocos (Keeling) Islands", 33 | "url": "https://upload.wikimedia.org/wikipedia/commons/e/e3/Clunies-Ross_family_arms.svg", 34 | "license": "Spedona, Clunies-Ross family arms, CC BY-SA 3.0" 35 | } 36 | }, 37 | { 38 | "country": { 39 | "name": "DR Congo", 40 | "url": "https://upload.wikimedia.org/wikipedia/commons/6/66/Coat_of_arms_of_the_Democratic_Republic_of_the_Congo_%28grey_spear%29.svg", 41 | "license": "Public domain" 42 | } 43 | }, 44 | { 45 | "country": { 46 | "name": "Egypt", 47 | "url": "https://upload.wikimedia.org/wikipedia/commons/a/a6/Coat_of_arms_of_Egypt_%28Official%29.svg", 48 | "license": "Public domain" 49 | } 50 | }, 51 | { 52 | "country": { 53 | "name": "Eswatini", 54 | "url": "https://upload.wikimedia.org/wikipedia/commons/a/a5/Coat_of_arms_of_Eswatini.svg", 55 | "license": "Public domain" 56 | } 57 | }, 58 | { 59 | "country": { 60 | "name": "Malta", 61 | "url": "https://upload.wikimedia.org/wikipedia/commons/e/eb/Coat_of_arms_of_Malta.svg", 62 | "license": "Public domain" 63 | } 64 | }, 65 | { 66 | "country": { 67 | "name": "Mayotte", 68 | "url": "https://upload.wikimedia.org/wikipedia/commons/b/bf/Coat_of_Arms_of_Mayotte.svg", 69 | "license": "Public domain" 70 | } 71 | }, 72 | { 73 | "country": { 74 | "name": "Niue", 75 | "url": "https://upload.wikimedia.org/wikipedia/commons/8/8f/Public_Seal_of_Niue.svg", 76 | "license": "Public domain" 77 | } 78 | }, 79 | { 80 | "country": { 81 | "name": "Norfolk Island", 82 | "url": "https://upload.wikimedia.org/wikipedia/commons/5/58/Coat_of_arms_of_Norfolk_Island.svg", 83 | "license": "quiresy92 including elements from Sodacan, Coat of arms of Norfolk Island, CC BY-SA 4.0" 84 | } 85 | }, 86 | { 87 | "country": { 88 | "name": "Northern Mariana Islands", 89 | "url": "https://upload.wikimedia.org/wikipedia/commons/4/4d/Seal_of_the_Northern_Mariana_Islands.svg", 90 | "license": "Public domain" 91 | } 92 | }, 93 | { 94 | "country": { 95 | "name": "Pitcairn Islands", 96 | "url": "https://upload.wikimedia.org/wikipedia/commons/c/c2/Coat_of_arms_of_the_Pitcairn_Islands.svg", 97 | "license": "Public domain" 98 | } 99 | }, 100 | { 101 | "country": { 102 | "name": "Puerto Rico", 103 | "url": "https://upload.wikimedia.org/wikipedia/commons/3/36/Coat_of_arms_of_the_Commonwealth_of_Puerto_Rico.svg", 104 | "license": "Heralder, Coat of arms of the Commonwealth of Puerto Rico, CC BY-SA 3.0" 105 | } 106 | }, 107 | { 108 | "country": { 109 | "name": "Republic of the Congo", 110 | "url": "https://upload.wikimedia.org/wikipedia/commons/d/d8/Coat_of_arms_of_the_Republic_of_the_Congo.svg", 111 | "license": "Public domain" 112 | } 113 | }, 114 | { 115 | "country": { 116 | "name": "Réunion", 117 | "url": "https://upload.wikimedia.org/wikipedia/commons/3/39/Armoiries_R%C3%A9union.svg", 118 | "license": "Superbenjamin, Armoiries Réunion, CC BY-SA 4.0" 119 | } 120 | }, 121 | { 122 | "country": { 123 | "name": "Saint Barthélemy", 124 | "url": "https://upload.wikimedia.org/wikipedia/commons/3/35/Blason_St_Barth%C3%A9l%C3%A9my_TOM_entire.svg", 125 | "license": "Manassas, Blason St Barthélémy TOM entire, CC BY-SA 3.0" 126 | } 127 | }, 128 | { 129 | "country": { 130 | "name": "Saint Helena, Ascension and Tristan da Cunha", 131 | "url": "https://upload.wikimedia.org/wikipedia/commons/c/c6/Coat_of_Arms_of_Saint_Helena.svg", 132 | "license": "P Di (they-them), Coat of Arms of Saint Helena, CC BY-SA 4.0 " 133 | } 134 | }, 135 | { 136 | "country": { 137 | "name": "Saint Martin", 138 | "url": "https://upload.wikimedia.org/wikipedia/commons/a/a6/Coat_of_arms_of_Saint_Martin.svg", 139 | "license": "Government of Collectivity of Saint-Martin, Coat of Saint-Martin, Licence Ouverte 2.0" 140 | } 141 | }, 142 | { 143 | "country": { 144 | "name": "Saint Pierre and Miquelon", 145 | "url": "https://upload.wikimedia.org/wikipedia/commons/1/1c/Coat_of_arms_of_Saint_Pierre_and_Miquelon.svg", 146 | "license": "Public domain" 147 | } 148 | }, 149 | { 150 | "country": { 151 | "name": "Sint Maarten", 152 | "url": "https://upload.wikimedia.org/wikipedia/commons/0/06/Sint_Maarten_eiland_wapen.svg", 153 | "license": "Public domain" 154 | } 155 | }, 156 | { 157 | "country": { 158 | "name": "South Georgia", 159 | "url": "https://upload.wikimedia.org/wikipedia/commons/7/78/Coat_of_arms_of_South_Georgia_and_the_South_Sandwich_Islands.svg", 160 | "license": "Public domain" 161 | } 162 | }, 163 | { 164 | "country": { 165 | "name": "Svalbard and Jan Mayen", 166 | "url": "https://upload.wikimedia.org/wikipedia/commons/8/8d/Coat_of_arms_of_the_Governor_of_Svalbard_%28historical%29.svg", 167 | "license": "Public domain" 168 | } 169 | }, 170 | { 171 | "country": { 172 | "name": "Syria", 173 | "url": "https://upload.wikimedia.org/wikipedia/commons/a/ae/Coat_of_arms_of_Syria_%282024%E2%80%93present%29_variation_media.svg", 174 | "license": "Public domain" 175 | } 176 | }, 177 | { 178 | "country": { 179 | "name": "Timor-Leste", 180 | "url": "https://upload.wikimedia.org/wikipedia/commons/b/bd/Coat_of_arms_of_East_Timor.svg", 181 | "license": "Public domain" 182 | } 183 | }, 184 | { 185 | "country": { 186 | "name": "Tokelau", 187 | "url": "https://upload.wikimedia.org/wikipedia/commons/4/42/Badge_of_the_General_Fono_of_Tokelau.svg", 188 | "license": "Public domain" 189 | } 190 | }, 191 | { 192 | "country": { 193 | "name": "Turks and Caicos Islands", 194 | "url": "https://upload.wikimedia.org/wikipedia/commons/f/f6/Coat_of_arms_of_the_Turks_and_Caicos_Islands.svg", 195 | "license": "Josedar, Coat of arms of the Turks and Caicos Islands, CC BY-SA 4.0" 196 | } 197 | }, 198 | { 199 | "country": { 200 | "name": "United States Minor Outlying Islands", 201 | "url": "https://upload.wikimedia.org/wikipedia/commons/5/5b/Greater_coat_of_arms_of_the_United_States.svg", 202 | "license": "Public domain" 203 | } 204 | }, 205 | { 206 | "country": { 207 | "name": "United States Virgin Islands", 208 | "url": "https://upload.wikimedia.org/wikipedia/commons/8/80/Seal_of_the_United_States_Virgin_Islands.svg", 209 | "license": "Public domain" 210 | } 211 | }, 212 | { 213 | "country": { 214 | "name": "Wallis and Futuna", 215 | "url": "https://upload.wikimedia.org/wikipedia/commons/9/91/Coat_of_arms_of_Wallis_and_Futuna.svg", 216 | "license": "Public domain" 217 | } 218 | }, 219 | { 220 | "country": { 221 | "name": "Western Sahara", 222 | "url": "https://upload.wikimedia.org/wikipedia/commons/4/40/Coat_of_arms_of_the_Sahrawi_Arab_Democratic_Republic.svg", 223 | "license": "Public domain" 224 | } 225 | } 226 | ] 227 | -------------------------------------------------------------------------------- /myproject/myapp/templates/countries.html: -------------------------------------------------------------------------------- 1 | 2 | {% load custom_filters %} 3 | {% load django_bootstrap5 %} 4 | {% load i18n %} 5 | {% load static %} 6 | 7 | 8 | 9 | 10 | 11 | REST Countries Info APP 12 | {% bootstrap_css %} 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

Countries of the World

24 |
25 | {{ search_form.search_term }} 26 | 29 |
    30 |
  • 31 |
32 |
33 | {% if not search_form.search_term.value %} 34 |
35 | 36 | {{ filter_form.regions }} 37 | 38 |
39 | {% else %} 40 | 42 | {% endif %} 43 |
44 |
45 |
46 |
47 | {% for country in country_data %} 48 |
49 | {{ country.name.common }} flag 50 |

{{ country.name.common }}

51 |

Capital: 52 | {% if country.capital %} 53 | {{ country.capital.0 }} 54 | {% else %} 55 | N/A 56 | {% endif %} 57 |

58 |

Population: 59 | {% if country.population %} 60 | {{ country.population }} 61 | {% else %} 62 | N/A 63 | {% endif %} 64 |

65 |

Region: 66 | {% if country.region %} 67 | {{ country.region }} 68 | {% else %} 69 | N/A 70 | {% endif %} 71 |

72 |

Subregion: 73 | {% if country.subregion %} 74 | {{ country.subregion }} 75 | {% else %} 76 | N/A 77 | {% endif %} 78 |

79 | {% if not search_form.search_term.value %} 80 | {% with country.name.common|get_emblems_url as emblems %} 81 | {% if emblems %} 82 | {{ country.name.common }} emblem 83 | {% else %} 84 | {{ country.name.common }} emblem 86 | {% endif %} 87 | {% endwith %} 88 | {% endif %} 89 | {% if search_form.search_term.value %} 90 |

Currency: 91 | {% if country.currencies %} 92 | {% for currency_code, currency_info in country.currencies.items %} 93 | {{ currency_info.name }}{% if not forloop.last %},{% endif %} 94 | {% endfor %} 95 | {% else %} 96 | N/A 97 | {% endif %} 98 |

99 |

Language: 100 | {% if country.languages %} 101 | {% for language_code, language_name in country.languages.items %} 102 | {{ language_name }}{% if not forloop.last %},{% endif %} 103 | {% endfor %} 104 | {% else %} 105 | N/A 106 | {% endif %} 107 |

108 |

Borders: 109 | {% if country.borders %} 110 | {% for border_country_code in country.borders %} 111 | {{ border_country_code }}{% if not forloop.last %},{% endif %} 112 | {% endfor %} 113 | {% else %} 114 | N/A 115 | {% endif %} 116 |

117 |

Area: 118 | {% if country.area %} 119 | {{ country.area }} km² 120 | {% else %} 121 | N/A 122 | {% endif %} 123 |

124 |

Timezones: 125 | {% if country.timezones %} 126 | {% for timezone in country.timezones %} 127 | {{ timezone }}{% if not forloop.last %},{% endif %} 128 | {% endfor %} 129 | {% else %} 130 | N/A 131 | {% endif %} 132 |

133 |

Top-level domain: 134 | {% if country.tld %} 135 | {{ country.tld.0 }} 136 | {% else %} 137 | N/A 138 | {% endif %} 139 |

140 | {% endif %} 141 |
142 | {% if search_form.search_term.value %} 143 | {% with country.name.common|get_emblems_url as emblems %} 144 | {% if emblems %} 145 |
146 | {{ country.name.common }} emblem 147 |
148 | {% elif country.coatOfArms %} 149 |
150 | {{ country.name.common }} emblem 152 |
153 | {% endif %} 154 | {% endwith %} 155 | {% endif %} 156 | {% if country.description %} 157 |
158 |

ABOUT {{ country.name.common|upper }}:

159 |

{{ country.description }}

160 |
161 | {% endif %} 162 | {% endfor %} 163 | {% if not country_data %} 164 |
165 |

No results found...

166 |
167 | {% endif %} 168 |
169 |
170 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /myproject/myapp/templates/filter_countries.html: -------------------------------------------------------------------------------- 1 | {% load custom_filters %} 2 | {% load static %} 3 | {% for country in country_data %} 4 |
5 | {{ country.name.common }} flag 6 |

{{ country.name.common }}

7 |

Capital: 8 | {% if country.capital %} 9 | {{ country.capital.0 }} 10 | {% else %} 11 | N/A 12 | {% endif %} 13 |

14 |

Population: 15 | {% if country.population %} 16 | {{ country.population }} 17 | {% else %} 18 | N/A 19 | {% endif %} 20 |

21 |

Region: 22 | {% if country.region %} 23 | {{ country.region }} 24 | {% else %} 25 | N/A 26 | {% endif %} 27 |

28 |

Subregion: 29 | {% if country.subregion %} 30 | {{ country.subregion }} 31 | {% else %} 32 | N/A 33 | {% endif %} 34 |

35 | {% if not search_form.search_term.value %} 36 | {% with country.name.common|get_emblems_url as emblems %} 37 | {% if emblems %} 38 | {{ country.name.common }} emblem 39 | {% else %} 40 | {{ country.name.common }} emblem 41 | {% endif %} 42 | {% endwith %} 43 | {% endif %} 44 |
45 | {% endfor %} 46 | -------------------------------------------------------------------------------- /myproject/myapp/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | {% load i18n %} 3 | {% load django_bootstrap5 %} 4 | {% load static %} 5 | 6 | 7 | 8 | 9 | 10 | Home 11 | 12 | 13 | 14 | 15 |
16 |
17 |

Welcome to the REST Countries Info APP

18 |

Click on the button below to start.

19 | 21 |

© 2024 | Davide Presti

22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /myproject/myapp/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from .models import Region, Country 3 | 4 | class RegionModelTestCase(TestCase): 5 | def setUp(self): 6 | self.region = Region.objects.create(name='Europe') 7 | 8 | def test_region_str_representation(self): 9 | self.assertEqual(str(self.region), 'Europe') 10 | 11 | class CountryModelTestCase(TestCase): 12 | def setUp(self): 13 | self.region = Region.objects.create(name='Europe') 14 | self.country = Country.objects.create( 15 | name='Portugal', 16 | capital='Lisbon', 17 | population=10229907, 18 | region=self.region, 19 | subregion='Southern Europe', 20 | currency='Euro', 21 | language='Portuguese', 22 | borders='ESP', 23 | area=92090.0, 24 | timezone='UTC-01:00', 25 | top_level_domain='.pt' 26 | ) 27 | 28 | def test_country_str_representation(self): 29 | self.assertEqual(str(self.country), 'Portugal') 30 | self.assertEqual(self.country.name, 'Portugal') 31 | self.assertEqual(self.country.capital, 'Lisbon') 32 | self.assertEqual(self.country.population, 10229907) 33 | self.assertEqual(self.country.region, self.region) 34 | self.assertEqual(self.country.subregion, 'Southern Europe') 35 | self.assertEqual(self.country.currency, 'Euro') 36 | self.assertEqual(self.country.language, 'Portuguese') 37 | self.assertEqual(self.country.borders, 'ESP') 38 | self.assertEqual(self.country.area, 92090.0) 39 | self.assertEqual(self.country.timezone, 'UTC-01:00') 40 | self.assertEqual(self.country.top_level_domain, '.pt') 41 | -------------------------------------------------------------------------------- /myproject/myapp/utils.py: -------------------------------------------------------------------------------- 1 | import unicodedata 2 | import requests 3 | import wikipediaapi 4 | from django.core.cache import cache 5 | 6 | # Function to normalize the search term to ASCII, but keep the special characters in the country names. 7 | def normalize_string(s): 8 | return ''.join(c for c in unicodedata.normalize('NFD', s) 9 | if unicodedata.category(c) != 'Mn') 10 | 11 | # Function to fetch and cache countries data. 12 | def fetch_and_cache_countries_data(): 13 | data = requests.get("https://restcountries.com/v3.1/all").json() 14 | cache.set('all_countries', data, 3600) # Cache data for 1 hour. 15 | return data 16 | 17 | # Function to retrieve cached countries data or fetch if not available. 18 | def get_cached_countries_data(): 19 | data = cache.get('all_countries') 20 | if data is None: 21 | data = fetch_and_cache_countries_data() 22 | return data 23 | 24 | # Function to exclude uninhabited territories from the data. 25 | def exclude_countries(data): 26 | return [country for country in data if country['name']['common'] and 'population' in country and country['population']] 27 | 28 | # Function to get autocomplete suggestions with normalized strings. 29 | def get_search_suggestions(data, search_term): 30 | search_country = normalize_string(search_term.lower()) 31 | return [country['name']['common'] for country in data if normalize_string(country['name']['common'].lower()).startswith(search_country)] 32 | 33 | # Function to filter countries by search term with normalized strings. 34 | def filter_countries_by_search_term(data, search_term): 35 | search_country = normalize_string(search_term.lower()) 36 | return [country for country in data if search_country == normalize_string(country['name']['common'].lower())] 37 | 38 | # Function to filter countries by region. 39 | def filter_countries_by_region(data, region): 40 | return [country for country in data if region in country.get('region', '')] 41 | 42 | # Function to handle special cases where the country name does not match the Wikipedia page title. 43 | def get_page_title(country_name): 44 | special_cases = { 45 | 'Georgia': 'Georgia (country)', 46 | 'Micronesia': 'Federated States of Micronesia', 47 | 'Palestine': 'State of Palestine', 48 | 'Western Sahara': 'Sahrawi Arab Democratic Republic', 49 | 'Saint Martin': 'Saint Martin (island)'} 50 | 51 | return special_cases.get(country_name, country_name) 52 | 53 | # Function to fetch country descriptions from Wikipedia. 54 | def fetch_country_descriptions(data): 55 | descriptions = {} 56 | wiki_wiki = wikipediaapi.Wikipedia('User Agent', extract_format=wikipediaapi.ExtractFormat.WIKI) 57 | 58 | for country in data: 59 | country_name = country['name']['common'] 60 | page_title = get_page_title(country_name) 61 | page = wiki_wiki.page(page_title) 62 | 63 | if page.exists(): 64 | descriptions[country_name] = page.summary 65 | 66 | return descriptions -------------------------------------------------------------------------------- /myproject/myapp/views.py: -------------------------------------------------------------------------------- 1 | import pyuca 2 | from .forms import RegionFilterForm, CountrySearchForm 3 | from django.http import JsonResponse 4 | from django.shortcuts import render 5 | from django.template.loader import render_to_string 6 | from .utils import get_cached_countries_data, exclude_countries, get_search_suggestions, filter_countries_by_search_term, filter_countries_by_region, fetch_country_descriptions 7 | 8 | # Initialize the collator for sorting. 9 | collator = pyuca.Collator() 10 | 11 | # Function to render the home page. 12 | def home(homepage): 13 | return render(homepage, 'index.html') 14 | 15 | # Function to filter countries by region and return the filtered data as a JSON response. 16 | def filter_regions(request): 17 | if request.method == 'GET': 18 | region = request.GET.get('region', '') 19 | data = get_cached_countries_data() 20 | data = exclude_countries(data) 21 | data.sort(key=lambda country: collator.sort_key(country['name']['common'])) 22 | 23 | if data: 24 | filtered_data = filter_countries_by_region(data, region) if region != 'All' else data 25 | return JsonResponse({'data': render_to_string('filter_countries.html', {'country_data': filtered_data})}) 26 | 27 | return JsonResponse({'data': 'Invalid request'}, status=400) 28 | 29 | # Function to search for countries. 30 | def search_countries(request): 31 | search_form = CountrySearchForm(request.GET) 32 | filter_form = RegionFilterForm(request.GET) 33 | 34 | if request.method == 'GET': 35 | search_term = request.GET.get('search_term', '') 36 | data = get_cached_countries_data() 37 | data = exclude_countries(data) 38 | data.sort(key=lambda country: collator.sort_key(country['name']['common'])) 39 | 40 | if 'autocomplete' in request.GET: 41 | suggestions = get_search_suggestions(data, search_term) 42 | return JsonResponse({'suggestions': suggestions}) 43 | 44 | if search_term: 45 | data = filter_countries_by_search_term(data, search_term) 46 | descriptions = fetch_country_descriptions(data) 47 | for country in data: 48 | country['description'] = descriptions.get(country['name']['common'], None) 49 | 50 | return render(request, 'countries.html', {'country_data': data, 'search_form': search_form, 'filter_form': filter_form}) 51 | 52 | # Function to display country information in the HTML template. 53 | def country_info(request): 54 | data = get_cached_countries_data() 55 | data = exclude_countries(data) 56 | data.sort(key=lambda country: collator.sort_key(country['name']['common'])) 57 | 58 | search_form = CountrySearchForm(request.GET) 59 | filter_form = RegionFilterForm(request.GET) 60 | 61 | search_term = request.GET.get('search_term', '') 62 | 63 | if search_term: 64 | data = filter_countries_by_search_term(data, search_term) 65 | descriptions = fetch_country_descriptions(data) 66 | for country in data: 67 | country['description'] = descriptions.get(country['name']['common'], None) 68 | 69 | elif filter_form.is_valid(): 70 | region = filter_form.cleaned_data.get('region', '') 71 | if region: 72 | data = filter_countries_by_region(data, region) 73 | 74 | return render(request, 'countries.html', {'country_data': data, 'search_form': search_form, 'filter_form': filter_form}) 75 | -------------------------------------------------------------------------------- /myproject/myproject/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davevad93/rest-countries-django-app/2f49ffbab8135c732a7a54a1cefd3aa265aa3711/myproject/myproject/__init__.py -------------------------------------------------------------------------------- /myproject/myproject/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for myproject project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /myproject/myproject/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for myproject project. 3 | 4 | Generated by 'django-admin startproject' using Django 5.0.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/5.0/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | from dotenv import load_dotenv 15 | import os 16 | 17 | load_dotenv() 18 | 19 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 20 | BASE_DIR = Path(__file__).resolve().parent.parent 21 | STATICFILES_DIRS = [BASE_DIR / 'myapp' / 'static'] 22 | STATIC_ROOT = BASE_DIR / 'staticfiles_build' 23 | 24 | # Quick-start development settings - unsuitable for production 25 | # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ 26 | 27 | # SECURITY WARNING: keep the secret key used in production secret! 28 | SECRET_KEY = os.environ.get('SECRET_KEY') 29 | 30 | # SECURITY WARNING: don't run with debug turned on in production! 31 | DEBUG = False 32 | 33 | # https://docs.djangoproject.com/en/3.0/ref/settings/#allowed-hosts 34 | ALLOWED_HOSTS = [] 35 | 36 | RENDER_EXTERNAL_HOSTNAME = os.environ.get('RENDER_EXTERNAL_HOSTNAME') 37 | if RENDER_EXTERNAL_HOSTNAME: 38 | ALLOWED_HOSTS.append(RENDER_EXTERNAL_HOSTNAME) 39 | 40 | # X-Content-Type-Options 41 | SECURE_BROWSER_XSS_FILTER = True 42 | SECURE_HSTS_SECONDS = 31536000 43 | SECURE_HSTS_INCLUDE_SUBDOMAINS = True 44 | SECURE_HSTS_PRELOAD = True 45 | SECURE_CONTENT_TYPE_NOSNIFF = True 46 | SECURE_SSL_REDIRECT = True 47 | 48 | # Application definition 49 | 50 | INSTALLED_APPS = [ 51 | 'django.contrib.admin', 52 | 'django.contrib.auth', 53 | 'django.contrib.contenttypes', 54 | 'django.contrib.sessions', 55 | 'django.contrib.messages', 56 | 'django.contrib.staticfiles', 57 | 'myapp', 58 | 'django_bootstrap5', 59 | ] 60 | 61 | MIDDLEWARE = [ 62 | 'django.middleware.security.SecurityMiddleware', 63 | 'whitenoise.middleware.WhiteNoiseMiddleware', 64 | 'django.contrib.sessions.middleware.SessionMiddleware', 65 | 'django.middleware.common.CommonMiddleware', 66 | 'django.middleware.csrf.CsrfViewMiddleware', 67 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 68 | 'django.contrib.messages.middleware.MessageMiddleware', 69 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 70 | 'django.middleware.locale.LocaleMiddleware', 71 | ] 72 | 73 | ROOT_URLCONF = 'myproject.urls' 74 | 75 | TEMPLATES = [ 76 | { 77 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 78 | 'DIRS': [], 79 | 'APP_DIRS': True, 80 | 'OPTIONS': { 81 | 'context_processors': [ 82 | 'django.template.context_processors.debug', 83 | 'django.template.context_processors.request', 84 | 'django.contrib.auth.context_processors.auth', 85 | 'django.contrib.messages.context_processors.messages', 86 | ], 87 | 'libraries':{ 88 | 'custom_filters': 'myapp.custom_filters', 89 | 90 | } 91 | }, 92 | }, 93 | ] 94 | 95 | WSGI_APPLICATION = 'myproject.wsgi.application' 96 | 97 | # Database 98 | # https://docs.djangoproject.com/en/5.0/ref/settings/#databases 99 | 100 | DATABASES = { 101 | 'default': { 102 | 'ENGINE': 'django.db.backends.sqlite3', 103 | 'NAME': BASE_DIR / 'db.sqlite3', 104 | } 105 | } 106 | 107 | # Password validation 108 | # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators 109 | 110 | AUTH_PASSWORD_VALIDATORS = [ 111 | { 112 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 113 | }, 114 | { 115 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 116 | }, 117 | { 118 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 119 | }, 120 | { 121 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 122 | }, 123 | ] 124 | 125 | # Internationalization 126 | # https://docs.djangoproject.com/en/5.0/topics/i18n/ 127 | 128 | LANGUAGE_CODE = 'en-us' 129 | TIME_ZONE = 'UTC' 130 | USE_TZ = True 131 | 132 | # Static files (CSS, JavaScript, Images) 133 | # https://docs.djangoproject.com/en/5.0/howto/static-files/ 134 | 135 | STATIC_URL = '/static/' 136 | 137 | # This production code might break development mode, so we check whether we're in DEBUG mode 138 | if not DEBUG: 139 | # Tell Django to copy static assets into a path called `staticfiles` (this is specific to Render) 140 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') 141 | # Enable the WhiteNoise storage backend, which compresses static files to reduce disk use 142 | # and renames the files with unique names for each version to support long-term caching 143 | STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' 144 | 145 | # Default primary key field type 146 | # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field 147 | 148 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 149 | -------------------------------------------------------------------------------- /myproject/myproject/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for myproject project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/5.0/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | from django.contrib import admin 18 | from django.urls import path 19 | from myapp import views 20 | 21 | urlpatterns = [ 22 | path('admin/', admin.site.urls), 23 | path('', views.home, name='home'), 24 | path("countries/", views.country_info, name='countries'), 25 | path('filter_regions/', views.filter_regions, name='filter_regions'), 26 | path('search_countries/', views.search_countries, name='search_countries'), 27 | ] 28 | -------------------------------------------------------------------------------- /myproject/myproject/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for myproject project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') 15 | 16 | application = get_wsgi_application() 17 | 18 | app = application -------------------------------------------------------------------------------- /myproject/requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.7.2 2 | beautifulsoup4==4.12.3 3 | certifi==2024.7.4 4 | charset-normalizer==3.3.2 5 | click==8.1.7 6 | colorama==0.4.6 7 | Django==5.0.13 8 | django-bootstrap5==23.4 9 | gunicorn==23.0.0 10 | h11==0.16.0 11 | idna==3.7 12 | packaging==24.2 13 | python-dotenv==1.0.1 14 | pyuca==1.2 15 | requests==2.32.4 16 | soupsieve==2.5 17 | sqlparse==0.5.0 18 | tzdata==2023.4 19 | urllib3==2.2.2 20 | uvicorn==0.32.1 21 | whitenoise==6.8.2 22 | Wikipedia-API==0.6.0 23 | -------------------------------------------------------------------------------- /myproject/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "builds": [ 3 | { 4 | "src": "myproject/wsgi.py", 5 | "use": "@vercel/python", 6 | "config": { "maxLambdaSize": "15mb", "runtime": "python3.12" } 7 | }, 8 | { 9 | "src": "build_files.sh", 10 | "use": "@vercel/static-build", 11 | "config": { 12 | "distDir": "staticfiles_build" 13 | } 14 | } 15 | ], 16 | "routes": [ 17 | { 18 | "src": "/static/(.*)", 19 | "dest": "/static/$1" 20 | }, 21 | { 22 | "src": "/(.*)", 23 | "dest": "myproject/wsgi.py" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /screenshots/card.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davevad93/rest-countries-django-app/2f49ffbab8135c732a7a54a1cefd3aa265aa3711/screenshots/card.gif -------------------------------------------------------------------------------- /screenshots/countries.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davevad93/rest-countries-django-app/2f49ffbab8135c732a7a54a1cefd3aa265aa3711/screenshots/countries.JPG -------------------------------------------------------------------------------- /screenshots/description.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davevad93/rest-countries-django-app/2f49ffbab8135c732a7a54a1cefd3aa265aa3711/screenshots/description.gif -------------------------------------------------------------------------------- /screenshots/filter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davevad93/rest-countries-django-app/2f49ffbab8135c732a7a54a1cefd3aa265aa3711/screenshots/filter.gif -------------------------------------------------------------------------------- /screenshots/homepage.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davevad93/rest-countries-django-app/2f49ffbab8135c732a7a54a1cefd3aa265aa3711/screenshots/homepage.JPG -------------------------------------------------------------------------------- /screenshots/search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davevad93/rest-countries-django-app/2f49ffbab8135c732a7a54a1cefd3aa265aa3711/screenshots/search.gif --------------------------------------------------------------------------------