├── .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 [](https://opensource.org/licenses/MIT)  
2 |
3 | 
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 | 
104 |
105 | ## - Características -
106 |
107 | - ### Busca los países por nombre.
108 |
109 | 
110 |
111 | - ### Filtra los países por región.
112 |
113 | 
114 |
115 | - ### Visualiza las informaciones detalladas de cada país.
116 |
117 | 
118 |
119 | - ### Función de búsqueda haciendo clic en las tarjetas.
120 |
121 | 
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 | - [](https://www.python.org)
130 | - [](https://www.djangoproject.com)
131 | - [](https://www.javascript.com)
132 | - [](https://getbootstrap.com)
133 | - [](https://html.com)
134 | - [](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 [](https://opensource.org/licenses/MIT)  
2 |
3 | 
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 | 
105 |
106 | ## - Caratteristiche -
107 |
108 | - ### Cerca i paesi per nome.
109 |
110 | 
111 |
112 | - ### Filtra i paesi per regione.
113 |
114 | 
115 |
116 | - ### Visualizza le informazioni dettagliate di ogni paese.
117 |
118 | 
119 |
120 | - ### Funzione di ricerca cliccando sulle tessere.
121 |
122 | 
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 | - [](https://www.python.org)
131 | - [](https://www.djangoproject.com)
132 | - [](https://www.javascript.com)
133 | - [](https://getbootstrap.com)
134 | - [](https://html.com)
135 | - [](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 [](https://opensource.org/licenses/MIT)  
2 |
3 | 
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 | 
105 |
106 | ## - Features -
107 |
108 | - ### Search for countries by name.
109 |
110 | 
111 |
112 | - ### Filter for countries by region.
113 |
114 | 
115 |
116 | - ### View detailed information about each country.
117 |
118 | 
119 |
120 | - ### Search feature by clicking on the cards.
121 |
122 | 
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 | - [](https://www.python.org)
131 | - [](https://www.djangoproject.com)
132 | - [](https://www.javascript.com)
133 | - [](https://getbootstrap.com)
134 | - [](https://html.com)
135 | - [](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 |
45 |
46 |
47 | {% for country in country_data %}
48 |
49 |

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 |

83 | {% else %}
84 |

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 |

147 |
148 | {% elif country.coatOfArms %}
149 |
150 |

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 |

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 |

39 | {% else %}
40 |

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
--------------------------------------------------------------------------------