Django Management Command for Countries with Flag & Cities

Anshuman Singh
5 min readFeb 3, 2020

Hello Djangonauts, While implementing Custom Countries data in django Application has always been tedious task i.e finding exact data Source/API/Package from where you can get usable data. Well i have created a management command that will get all the countries including non-UN members and their flags along with cities. Learn how to create basic Custom Management Command.

load_country.py

from django.core.management.base import BaseCommand 
from django.utils import timezone
class Command(BaseCommand):

def handle(self, *args, **kwargs):
Print("its working")

After you’ve set up a custom command in your Django project, command file should look like this and when you run ./manage.py load_country you will get “Its working” as output. So basic setup done.

Now lets look at the data source from where we will be getting the data.

They have provided a free version of data that is available in Excel/CSV format which is pretty easy to work with using python, but i have created JSON version of the same data which can be used as an open API to fetch countries and its cities along with few additional data. We will be using the same.

{
"city": "Tokyo",
"city_ascii": "Tokyo",
"lat": 35.685,
"lng": 139.7514,
"country": "Japan",
"iso2": "JP",
"iso3": "JPN",
"admin_name": "Tōkyō",
"capital": "primary",
"population": 35676000,
"id": 1392685764
}

This is a sample JSON object for city Tokyo. Yes, data is city wise with additional data. So the logic here is we save countries and cities simultaneously in two different models Country & City.

models.py

class Country(models.Model):
name = models.CharField(null=True, blank=True, max_length=255)
iso2 = models.CharField(max_length=10, null=True, blank=True)
iso3 = models.CharField(max_length=10, null=True, blank=True)
flag = models.ImageField(null=True, blank=True)
class City(models.Model):
city = models.CharField(max_length=50)
city_ascii = models.CharField(max_length=50, null=True, blank=True)
country = models.ForeignKey(Country, on_delete=models.CASCADE, null=True, blank=True)

def __str__(self):
return self.city_ascii

Keeping null & blank as True ensures that if there’s any faulty data, it won’t break your DB.

import requests 
from django.core.management.base import BaseCommand

class Command(BaseCommand):
def handle(self, *args, **kwargs):
country_data = requests.get("https://gist.githubusercontent.com/ans2human/89f78752e161219060257b160f970fcd/raw/50d755da33db30ecb533d1770d94f9adcc8d6892/world_cities.json")
cntry_dataset = country_data.json()
print(cntry_dataset)

Now let's make a request to the JSON API and get dataset. If you print cntry_dataset you will see the JSON of around 15000 cities with their country, ISO and ISO2 which we are going to insert it in our DB.

import requests 
from django.core.management.base import BaseCommand
from appname.models import Country, City
class Command(BaseCommand):
def handle(self, *args, **kwargs):
country_data = requests.get("https://gist.githubusercontent.com/ans2human/89f78752e161219060257b160f970fcd/raw/50d755da33db30ecb533d1770d94f9adcc8d6892/world_cities.json")
cntry_dataset = country_data.json()
for i, countries in enumerate(cd):
country_obj, created = Country.objects.update_or_create(name=countries.get('country'), iso2=countries.get('iso2'), iso3=countries.get('iso3'))
city = countries.get('city')
city_ascii = countries.get('city_ascii')
print(country_obj.id, country_obj.name, city, city_ascii)
City.objects.create(country=country_obj, city=city, city_ascii=city_ascii)

Now import your models in the command and loop through the cities JSON objects and start creating city and Country object by using django create method. Here we are creating Country object first using update_or_create for fields from the Country model, for which data comes from iterated city JSON object from where we take out country, iso2, iso3 using python get method for dictionary. Similarly, we do it for City model, get the relevant data and put it in create query. After running the above code you will see countries getting printed along with cities. It will take some time as its going to insert 15000+ cities in your database all linked to their countries via ForeignKey.

Flags of countries

Now that country part is done, we need to start saving flags for the respective countries. As you must have noticed we have flag field in our Country model which has null and blank so that it is empty while saving countries and then we can iterate through model objects and update them with image of flag corresponding to its country.

Lets look at the source from where we’re supposed to get the flag images.

https://www.countryflags.io/

https://www.countryflags.io/country_code/style/size.png

Getting Image from their public API is pretty straight forward. So using the above API you just need to put ISO2 code instead of country_code which we have already saved for every country from your country JSON data and put 64 instead of size for all the countries as that is the size of image we want. you can switch to smaller size like 48, 32, 24 and 16.

So now how do we integrate Flag saving code in the same command, well Django is very well designed in a way that you can almost override anything throughout the framework, therefore we will use the functionalities provided for management commands i.e Arguments, handle method will be taking arguments using which we can execute a part of our management command which will be flags as it needs to be run after the countries are saved or else it will raise an exception which is handled and prompts to load countries first.

def add_arguments(self, parser):
parser.add_argument('--flag', action='store_true', help="Update country data with their flags")

Add this in your command file, using this argument we’ll be able to execute only flag related code block. So now lets work on that.

qs = Country.objects.all().iterator()
for qs in qs:
url = "https://www.countryflags.io/{}/flat/64.png".format((qs.iso2).lower())
img = requests.get(url, stream=True)
if img.status_code != requests.codes.ok:
continue
fp = BytesIO()
fp.write(img.content)
filename = "{}.png".format((qs.iso2).lower())
qs.flag.save(filename, files.File(fp))
print(qs.id, qs.iso2)

So here we iterate through the Country objects and call the flag api with each iteration of object which gets us the ISO2 code for country and we format that or you can simply concat with a variable. After that we allot the image file a temporary memory in server and then save the image with its ISO2 name.

Therefore put this block of code in a if else so that when you put — flag in front of command it should run the flag code block:

class Command(BaseCommand):

def add_arguments(self, parser):
parser.add_argument('--flag', action='store_true', help="Update country data with their flags")
def handle(self, *args, **kwargs):
worldcities_csv = kwargs.get('flag')
if worldcities_csv:
#flag code block from above goes here else: #country & cities code block

Wrap Up

To run country you have to run ./manage load_country
To get flags for the countries you run ./manage.py load_country — flag

Complete management command code should look like this almost.

Let me know if i missed anything. Thankyou.

--

--