Does the django_address module provide a way to seed the initial country data?
Asked Answered
M

5

7

I'm using Django 2.0, Python 3.7, and MySql 5. I recently installed the django_address module. I noticed when I ran my initial migration based on my models.py file ...

from django.db import models

from address.models import AddressField
from phonenumber_field.modelfields import PhoneNumberField


class CoopType(models.Model):
    name = models.CharField(max_length=200, null=False)

    class Meta:
        unique_together = ("name",)


class Coop(models.Model):
    type = models.ForeignKey(CoopType, on_delete=None)
    address = AddressField(on_delete=models.CASCADE)
    enabled = models.BooleanField(default=True, null=False)
    phone = PhoneNumberField(null=True)
    email = models.EmailField(null=True)
    web_site = models.TextField()

It created some address tables, including ...

mysql> show create table address_country;
+-----------------+---------------------------------------------------+
| Table           | Create Table                                      |
+-----------------+---------------------------------------------------+
| address_country | CREATE TABLE `address_country` (                  |
|                 |   `id` int(11) NOT NULL AUTO_INCREMENT,           |
|                 |   `name` varchar(40) COLLATE utf8_bin NOT NULL,   |
|                 |   `code` varchar(2) COLLATE utf8_bin NOT NULL,    |
|                 |   PRIMARY KEY (`id`),                             |
|                 |   UNIQUE KEY `name` (`name`)                      |
|                 | ) ENGINE=InnoDB                                   |
|                 | DEFAULT CHARSET=utf8 COLLATE=utf8_bin             |
+-----------------+---------------------------------------------------+

However, this table has no data in it. Is there a way to obtain seed data for the table generated by the module or do I need to dig it up on my own?

Meridethmeridian answered 27/1, 2020 at 20:42 Comment(3)
Which django_address are you using if this one?Wearing
Yes that's the one! Of course if there is a better module that crates an AddressField model type as well as supplying some default seed info, I'm willing to switch.Meridethmeridian
What is your intent on using django_address? Do you want to know the Coop's entire address or just their Country? Can you explain more about what you expected to achieve?Wearing
O
0

I'd suggest you write a simple management command that imports data from pycountry into your address models (approach borrowed from here). pycountry is a wrapper around the ISO standard list of countries - i.e., it's about as canonical a list of countries as you're going to get.

A management command to populate all the countries into your Country model would look something like this:

import pycountry
from django.core.management.base import BaseCommand, CommandError

from address.models import Country

class Command(BaseCommand):
    help = "Populates address.Country with data from pycountry."

    def handle(self, *args, **options):
        countries = [
            Country(
                code=country.alpha_2,
                name=country.name[:40],  # NOTE - concat to 40 chars because of limit on the model field
            )
            for country in pycountry.countries
        ]

        Country.objects.bulk_create(countries)
        self.stdout.write("Successfully added %s countries." % len(countries))

This will populate your model with the ISO list of countries.

One caveat here is that the address.Country.name field is limited to 40 characters (which seems like a questionable design decision to me, along with the decision not to make country codes unique - ISO 2-letter codes definitely are unique), so the script above truncates the name to fit. If this is a problem for you I suggest you set up your own address models, borrowing from django-address and raising the character limit.

Orthodoxy answered 1/2, 2020 at 7:0 Comment(1)
(I noticed after posting this that bdoubleu has already offered a very similar solution. This answer will address the issue that there is a character limit on the name field).Orthodoxy
K
5

You can generate the countries yourself pretty easily with the pycountry package.

Since the code field on the Country model that is created has a maximum length of two characters then you'll want to use the alpha_2 code.

I usually use a custom management command for this sort of thing. Maybe add a check to see if any objects have already been created then handle as you'd like.

Usage from the shell python manage.py create_countries

from address.models import Country
from pycountry import countries
from django.core.management.base import BaseCommand

class Command(BaseCommand):
    help = 'Initialize Country model'

    def handle(self, *args, **kwargs):
        create_countries = [
            Country(name=country.name[:40], code=country.alpha_2)
            for country in countries
        ]
        Country.objects.bulk_create(create_countries)
        self.stdout.write(f'Created {len(countries)} countries.\n')

If the production server isn't running Python/Django then you could use pycountry to create a CSV file with the relevant data. Assuming you're using PostgreSQL then you could use the COPY FROM command to populate the database.

import csv
from pycountry import countries

with open('countries.csv', mode='w') as countries_file:
    # specify delimiter because some countries have a comma
    writer = csv.writer(countries_file, delimiter='\t')
    writer.writerow(['id', 'name', 'code'])
    writer.writerows([
        [index + 1, country.name, country.alpha_2]
        for index, country in enumerate(countries)
    ])
Killie answered 30/1, 2020 at 16:13 Comment(5)
Thanks, I guess this gets me closer. I wanted the data in the db because I'm going to do exports to another system that may or may not use Python. So your suggestion is to use the above script to generate a migration to populate the database?Meridethmeridian
The above script will generate 249 records in the database - one for each country.Killie
K, so I installed pycountry (pycountry-19.8.18.tar.gz) and when I ran the above script you had it died with the error, "django.db.utils.DataError: (1406, "Data too long for column 'name' at row 196")".Meridethmeridian
@Meridethmeridian ah ok, just looked at your OP and seems like that max chars for the name field is 40, I've added an edit that will slice the string at 40 charactersKillie
@Meridethmeridian interesting choice for the answer that got the bounty... lolKillie
S
0

As discussed above you need to add the actual data yourselves. You will need to prep it and do a one time upload. If you are looking for only country data, this is a good source. There is also a django app called django-countries which lets you have a lot more data and controls including flags, ISO codes etc. Another database with 3 letter codes is the IBAN list. Hope that helps.

Saber answered 30/1, 2020 at 5:13 Comment(2)
I was looking at that django-countries repo you linked to but I don't see any seed data or migrations in there. I was expecting to find a fixtures folder or something with the python data. Am I missing something?Meridethmeridian
I recommend you use the other two sources for seed data. Django-countries is for you to use as a well tested drop in for countries data. Also check this section out github.com/SmileyChris/django-countries/blob/master/… . The ioc codes are there in the same folder.Saber
R
0

You could use django-countries and include a CountryField in your model. This includes support for both models and a choice field for forms.

Since this is built in you could include this in your model just fine without having to worry about seeding a table.

Relume answered 31/1, 2020 at 14:47 Comment(2)
This doesn't answer the OP question in any way, also this package was already suggested below.Wearing
@CalMac, So I understand your suggestion, does the django-address module I listed above have any compatibility with the django-countries model you listed? Getting a list of coutnries is great, but with your module, how does that tie into the django-address stuff?Meridethmeridian
O
0

I'd suggest you write a simple management command that imports data from pycountry into your address models (approach borrowed from here). pycountry is a wrapper around the ISO standard list of countries - i.e., it's about as canonical a list of countries as you're going to get.

A management command to populate all the countries into your Country model would look something like this:

import pycountry
from django.core.management.base import BaseCommand, CommandError

from address.models import Country

class Command(BaseCommand):
    help = "Populates address.Country with data from pycountry."

    def handle(self, *args, **options):
        countries = [
            Country(
                code=country.alpha_2,
                name=country.name[:40],  # NOTE - concat to 40 chars because of limit on the model field
            )
            for country in pycountry.countries
        ]

        Country.objects.bulk_create(countries)
        self.stdout.write("Successfully added %s countries." % len(countries))

This will populate your model with the ISO list of countries.

One caveat here is that the address.Country.name field is limited to 40 characters (which seems like a questionable design decision to me, along with the decision not to make country codes unique - ISO 2-letter codes definitely are unique), so the script above truncates the name to fit. If this is a problem for you I suggest you set up your own address models, borrowing from django-address and raising the character limit.

Orthodoxy answered 1/2, 2020 at 7:0 Comment(1)
(I noticed after posting this that bdoubleu has already offered a very similar solution. This answer will address the issue that there is a character limit on the name field).Orthodoxy
R
0

Simple is better. By the django doc, you can create a data migration: https://docs.djangoproject.com/en/3.0/topics/migrations/#data-migrations

Assume the app name of your models.py is coops, do something like this:

First:

Second:

  • Add django_countries to INSTALLED_APPS

Third:

  • Make an empty migration file: python manage.py makemigrations --empty coops

Forth:

  • Edit the migration file, for example: vi coops/migrations/0002_auto_20200205_0421.py
  • file content:
# Generated by Django 2.2 on 2020-02-05 04:21

from django.db import migrations


def init_countries(apps, schema_editor):
    from django_countries import countries
    from address.models import Country
    countries = [
        Country(code=code, name=name) for code, name in countries
    ]
    Country.objects.bulk_create(countries)



class Migration(migrations.Migration):

    dependencies = [
        ('coops', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(init_countries),
    ]

Fifth:

  • run python manage.py migrate

After migrate address_country table should have data as follows:

In [1]: from address.models import *

In [2]: Country.objects.all()
Out[2]: <QuerySet [<Country: Afghanistan>, <Country: Albania>, <Country: Algeria>, <Country: American Samoa>, <Country: Andorra>, <Country: Angola>, <Country: Anguilla>, <Country: Antarctica>, <Country: Antigua and Barbuda>, <Country: Argentina>, <Country: Armenia>, <Country: Aruba>, <Country: Australia>, <Country: Austria>, <Country: Azerbaijan>, <Country: Bahamas>, <Country: Bahrain>, <Country: Bangladesh>, <Country: Barbados>, <Country: Belarus>, '...(remaining elements truncated)...']>

In [3]: Country.objects.all().count()
Out[3]: 249
Recognition answered 5/2, 2020 at 4:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.