scibi / django-teryt

Django app that implements TERYT database
MIT License
3 stars 7 forks source link

Nieprawidłowy import SIMC.xml #8

Closed ad-m closed 9 years ago

ad-m commented 9 years ago

Szanowny,

$ DJANGO_DEBUG=False python manage.py teryt_parse SIMC.xml 
CommandError: Database integrity error: (1452, 'Cannot add or update a child row: a foreign key constraint fails (`feder`.`teryt_miejscowosc`, CONSTRAINT `ter_miejscowosc_nadrzedna_id_34eba95_fk_teryt_miejscowosc_symbol` FOREIGN KEY (`miejscowosc_nadrzedna_id`) REFERENCES `teryt_miejscowosc` (`symbol`))')

Coś chyba się popsuło w danych wejściowych. WMRODZ.xml i TERC.xml importuje się prawidłowo. Nie mogę tego debugować teraz.

Z poważaniem i jawnością!

scibi commented 9 years ago

Hmm SOA#1: Dziwne, u mnie działa ;)

Sprawdziłem na danych pobranych dzisiaj, import do pustej bazy danych pobranych dzisiaj:

$ python manage.py teryt_parse ../dane/20150716/WMRODZ.xml
$ python manage.py teryt_parse ../dane/20150716/TERC.xml 
$ python manage.py teryt_parse ../dane/20150716/SIMC.xml 
$ python manage.py teryt_parse ../dane/20150716/ULIC.xml 

Import robiłeś na jakiej wersji kodu? Prosto z repo czy może ze swoimi poprawkami? Może wprowadziłeś jakieś zmiany z powodu #6 i coś z transakcjami się pomieszało?

ad-m commented 9 years ago

Hmm… PostgreSQL, czy MySQL? W moim przypadku MySQL.

Skasowałem forka i modyfikowałem tylko testy.

from os import path
import zipfile
import requests
from django.test import TestCase

class TestCommand(TestCase):
    tmp_dir = '/tmp/'
    files = {
        'ULIC.xml': 'http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=1246',
        'TERC.xml': 'http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=1110',
        'SIMC.xml': 'http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=1112',
        'WMRODZ.xml': 'http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=941'
    }

    @staticmethod
    def _save_file(filename, url):
        filepath = path.join(TestCommand.tmp_dir, filename+".zip")
        with open(filepath, 'wb') as handle:
            response = requests.get(url, stream=True)
            if response.ok:
                for block in response.iter_content(1024):
                    handle.write(block)
        with open(filepath) as handle:
            myzipfile = zipfile.ZipFile(handle)
            myzipfile.extract(filename, TestCommand.tmp_dir)

    def setUp(self):
        # _, tmp_dir = mkstemp()
        for filename, url in self.files.items():
            TestCommand._save_file(filename, url)

    def test_command(self):
        for filename in self.files.keys():
            management.call_command('teryt_parse', path.join(self.tmp_dir, filename))

Daje wynik:


ERROR: test_command (teryt.tests.test_models.TestCommand)

----------------------------------------------------------------------

Traceback (most recent call last):

  File "/home/travis/build/ad-m/django-teryt/teryt/tests/test_models.py", line 196, in test_command

    management.call_command('teryt_parse', path.join(self.tmp_dir, filename), stderr=a)

  File "/home/travis/build/ad-m/django-teryt/.tox/py27-django-17/lib/python2.7/site-packages/django/core/management/__init__.py", line 115, in call_command

    return klass.execute(*args, **defaults)

  File "/home/travis/build/ad-m/django-teryt/.tox/py27-django-17/lib/python2.7/site-packages/django/core/management/base.py", line 338, in execute

    output = self.handle(*args, **options)

  File "/home/travis/build/ad-m/django-teryt/.tox/py27-django-17/lib/python2.7/site-packages/django/db/transaction.py", line 454, in inner

    return func(*args, **kwargs)

  File "/home/travis/build/ad-m/django-teryt/teryt/management/commands/teryt_parse.py", line 56, in handle

    raise CommandError("Database integrity error: {}".format(e))

CommandError: Database integrity error: (1452, 'Cannot add or update a child row: a foreign key constraint fails (`test_django_teryt`.`teryt_miejscowosc`, CONSTRAINT `D3c5364cfbb51df1bef2196d31f93e17` FOREIGN KEY (`jednostka_id`) REFERENCES `teryt_jednostkaadministracyjna` (`id`))')

Stwierdziłem na python 2.7 django 1.{6,7}. Z powodu #6 nie dotyczy django 1.8. Nie skonfigurowałem MySQL z python 3.4.

scibi commented 9 years ago

Nigdy z MySQLem nie testowałem tej appki (ale planuję). Natomiast co do samego testu:

Python 2.7.3 (default, Mar 13 2014, 11:03:55) 
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> files = {
...         'ULIC.xml': 'http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=1246',
...         'TERC.xml': 'http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=1110',
...         'SIMC.xml': 'http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=1112',
...         'WMRODZ.xml': 'http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=941'
...     }
>>> for filename in files.keys():
...   print filename
... 
SIMC.xml
ULIC.xml
WMRODZ.xml
TERC.xml

Tymczasem ważna jest kolejność wczytywania plików. Musi być tak:

  1. WMRODZ.xml
  2. TERC.xml
  3. SIMC.xml
  4. ULIC.xml
ad-m commented 9 years ago

No to …

from collections import OrderedDict
from tempfile import mkstemp
from os import path
import zipfile
import requests
from django.utils import six
from django.test import TestCase
from django.db import models
from django.core import management

class TestCommand(TestCase):
    tmp_dir = '/tmp/'
    files = OrderedDict([
         ('WMRODZ.xml','http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=941'),
         ('TERC.xml', 'http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=1110'),
         ('SIMC.xml','http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=1112'),
         ('ULIC.xml','http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=1246'),
     ])
    @staticmethod
    def _save_file(filename, url):
        filepath = path.join(TestCommand.tmp_dir, filename+".zip")
        with open(filepath, 'wb') as handle:
            response = requests.get(url, stream=True)
            if response.ok:
                for block in response.iter_content(1024):
                    handle.write(block)
        with open(filepath) as handle:
            myzipfile = zipfile.ZipFile(handle)
            myzipfile.extract(filename, TestCommand.tmp_dir)

    def setUp(self):
        # _, tmp_dir = mkstemp()
        for filename, url in self.files.items():
            TestCommand._save_file(filename, url)

    def test_command(self):
        for filename in self.files.keys():
            management.call_command('teryt_parse', path.join(self.tmp_dir, filename))
In [1]: from collections import OrderedDict

In [2]: files = OrderedDict([
   ...:          ('WMRODZ.xml','http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=941'),
   ...:          ('TERC.xml', 'http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=1110'),
   ...:          ('SIMC.xml','http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=1112'),
   ...:          ('ULIC.xml','http://www.stat.gov.pl/broker/access/prefile/downloadPreFile.jspa?id=1246'),
   ...:      ])

In [3]: files.keys()
Out[3]: ['WMRODZ.xml', 'TERC.xml', 'SIMC.xml', 'ULIC.xml']

Podczas testu:

CommandError: Database integrity error: (1452, 'Cannot add or update a child row: a foreign key constraint fails (`test_django_teryt`.`teryt_miejscowosc`, CONSTRAINT `D2feb4f7508a1889b1c1f2d22b639128` FOREIGN KEY (`miejscowosc_nadrzedna_id`) REFERENCES `teryt_miejscowosc` (`symbol`))')
scibi commented 9 years ago

No to teraz mamy rzeczywisty problem, ale jak domyślam się wynika on z faktu, że MySQL jest niekoszerny tj niezgodny ze standardem ;)

Wg standardu domyślnie FK powinny być sprawdzane dopiero w czasie commita. A tymczasem MySQL nie potrafi opóźnić sprawdzania FK i robi to bezpośrednio w czasie wstawiania rekordu do bazy: "InnoDB checks foreign key constraints immediately; the check is not deferred to transaction commit. According to the SQL standard, the default behavior should be deferred checking." (http://dev.mysql.com/doc/refman/5.7/en/innodb-foreign-key-constraints.html).

Trzeba by więc posortować dane przed wstawianiem. Myślę, wystarczy proste sortowanie: najpierw wszystko co nie ma miejscowości nadrzędnej a później wszystko co ma. Chyba, że okaże się, że w SIMC są części części miejscowości - to trzeba będzie sprytniejszy algorytm sortowania zrobić.

Kolejny punkt na liście TODO do refaktoringu.

ad-m commented 9 years ago

Na https://travis-ci.org/ad-m/django-teryt/builds/71495108 jest buildlog dla mojego forka wspierającego MySQL-a. Obrazuje on omawiany problem.

Z względu na driver mysql testy oblewają dla python 3.4. Natomiast testy integracyjne dla python 2.7 są wiarygodne.

ghost commented 8 years ago

Try to turn off foreign key constraint globally for mysql, do the following: SET GLOBAL FOREIGN_KEY_CHECKS=0; and remember to set it back when you are done SET GLOBAL FOREIGN_KEY_CHECKS=1;