Почему выдает ошибку в django model?

Выдает такую вот ошибку, в чем дело? RuntimeWarning: Accessing the database during app initialization is discouraged. To fix this warning, avoid executing queries in AppConfig.ready() or when your app modules are imported. warnings.warn(self.APPS_NOT_READY_WARNING_MSG, category=RuntimeWarning)

вот мой код в файле models.py:

from clickhouse_backend import models 
import uuid
from main.parser import data

class Currency(models.ClickhouseModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    date = models.DateField()
    unit = models.Int128Field()
    rate = models.Float64Field()

    class Meta:
        engine = models.MergeTree(
        order_by=["id"]
        )

for el in data:
    el_to_db = Currency(date = el['date'], unit=el['unit'], rate=el['rate'])
    el_to_db.save()

также сам файл парсер:

from urllib.request import urlopen
from bs4 import BeautifulSoup
from pathlib import Path
from datetime import datetime

inner_html_code = str(urlopen('https://www.cbr.ru/currency_base/dynamics/?UniDbQuery.Posted=True&UniDbQuery.so=1&UniDbQuery.mode=1&UniDbQuery.date_req1=&UniDbQuery.date_req2=&UniDbQuery.VAL_NM_RQ=R01239&UniDbQuery.From=01.01.2025&UniDbQuery.To=20.12.2025').read(),'utf-8')
inner_soup = BeautifulSoup(inner_html_code, "html.parser")
inner_soup = inner_soup.find('div', {"class": 'table-wrapper'})

# очищаем код от выбранных элементов
def delete_div(code,tag,arg):
     # находим все указанные теги с параметрами
     for div in code.find_all(tag, arg): 
        # и удаляем их из кода
        div.decompose()

delete_div(inner_soup, "div", {'class':'table-caption'})
delete_div(inner_soup, "td", {'colspan':'3'})

array = []

for tag in inner_soup.select('td'):
        # добавляем адрес ссылки в нашу общую переменную
        array.append(tag.get_text(strip=True))

array_str = str(array)


array_str = array_str.replace('[', '').replace(']', '').replace("'", "").replace(", ", " ").replace(",", ".")

file = open('array.txt', 'w')
file.write(array_str)
file.close()


def get_array_info(path):
    file_with_array = open(path, 'r')
    array_rows = file_with_array.readlines()
    for array_rows in array_rows:
        array_rows = array_rows.split(" ")
        n=3
        array_rows = [array_rows[i:i + n] for i in range(0, len(array_rows), n)]

        rows_list = []

        for row in array_rows:
                row_dict = {}
                row_dict['date'] = datetime.strptime(row[0], '%d.%m.%Y').date()
                row_dict['unit'] = int(row[1])
                row_dict['rate'] = float(row[2])
                rows_list.append(row_dict)

        file_with_array.close()
        return rows_list

path_with_file = Path(r'C:\Users\Никита\Desktop\Django\Курс валют\venv\app\array.txt')

data = get_array_info(path_with_file)

Django во время инициализации, помимо прочего, импортирует модули models из приложений и регистрирует объявленные там модели. А во время импорта модуля выполняется весь код, который не завёрнут в функции - то есть он выполняется в тот момент, когда Django ещё не завершил свою инициализацию.

В строке el_to_db = Currency(...), которая не находится ни в какой функции, вы пытаетесь создавать объекты БД в тот момент, когда сам модуль models ещё продолжает инициализироваться - то есть пока сам Django ещё запускается. Так делать нельзя, работа с моделями должна выполняться строго после завершения инициализации Django.

Вообще, выполнять какую-либо работу вне функций - плохой тон. По-хорошему место начала работы должно быть строго одно - блок if __name__ == "__main__": в скрипте, который является точкой входа.

В Django такой точкой входа обычно является скрипт manage.py (или django-admin.py по вкусу), поэтому писать свою точку входа вам не нужно. Вместо этого стоит создать management-команду и уже в ней вызвыать функции, выполняющие нужную работу.

  • Для начала стоит перестать запускать парсер во время инициализации - уберите строку data = get_array_info(path_with_file), она там ни к чему.

  • Глобальную переменую path_with_file также стоит убрать - вместо неё стоит использовать аргумент командной строки, что позволит использовать разные файлы для парсинга без необходимости исправлять код.

  • В файле models.py уберите блок for el in data:, чтобы не пытаться использовать модели до завершения инициализации Django.

  • Рядом с файлом models.py создайте каталог management, в нём каталог commands, а в нём два файла - management/commands/__init__.py (оставьте его пустым, он просто по традиции объявляет Python-пакет) и management/commands/parse.py, где parse будет названием команды.

Код файла parse.py может выглядеть примерно так (замените вашеприложение на название Django-приложения, в котором находятся модели):

from django.core.management.base import BaseCommand

from main.parser import get_array_info
from вашеприложение.models import Currency


class Command(BaseCommand):
    help = 'Runs the parser'

    def add_arguments(self, parser):
        parser.add_argument('path_to_file', help='Path to file')

    def handle(self, *args, **options):
        # Django вызовет эту функцию в правильный момент после своей инициализации
        path_to_file = options['path_to_file']
        data = get_array_info(path_to_file)  # Парсер запускается здесь
        for el in data:
            # Модели используются здесь
            el_to_db = Currency(date = el['date'], unit=el['unit'], rate=el['rate'])
            el_to_db.save()

Теперь можно запустить команду и указать путь к файлу:

python manage.py parse "C:\Users\Никита\Desktop\Django\Курс валют\venv\app\array.txt"

Django вызовет функцию add_arguments, в которой команда объявляет, что она готова принять один аргумент командной строки, а после полной инициализации вызовет функцию handle, в которой options['path_to_file'] вытаскивает аргумент по его названию, а дальше можно делать что угодно.

Подробнее в документации: https://docs.djangoproject.com/en/5.2/howto/custom-management-commands/


Весь код, связанный с urlopen, inner_soup и 'array.txt', тоже нужно завернуть в функцию и запускать через management-команду, которую можно назвать, например, download_and_save, а вместо 'array.txt' получать название сохраняемого файла из аргумента командной строки. Но чтобы не удлиннять и без того длинный ответ, я не стану это делать и оставлю в качестве домашнего задания автору вопроса.

Django во время инициализации, помимо прочего, импортирует модули models из приложений и регистрирует объявленные там модели. А во время импорта модуля выполняется весь код, который не завёрнут в функции - то есть он выполняется в тот момент, когда Django ещё не завершил свою инициализацию.

В строке el_to_db = Currency(...), которая не находится ни в какой функции, вы пытаетесь создавать объекты БД в тот момент, когда сам модуль models ещё продолжает инициализироваться - то есть пока сам Django ещё запускается. Так делать нельзя, работа с моделями должна выполняться строго после завершения инициализации Django.

Вообще, выполнять какую-либо работу вне функций - плохой тон. По-хорошему место начала работы должно быть строго одно - блок if __name__ == "__main__": в скрипте, который является точкой входа.

В Django такой точкой входа обычно является скрипт manage.py (или django-admin.py по вкусу), поэтому писать свою точку входа вам не нужно. Вместо этого стоит создать management-команду и уже в ней вызвыать функции, выполняющие нужную работу.

  • Для начала стоит перестать запускать парсер во время инициализации - уберите строку data = get_array_info(path_with_file), она там ни к чему.

  • Глобальную переменую path_with_file также стоит убрать - вместо неё стоит использовать аргумент командной строки, что позволит использовать разные файлы для парсинга без необходимости исправлять код.

  • В файле models.py уберите блок for el in data:, чтобы не пытаться использовать модели до завершения инициализации Django.

  • Рядом с файлом models.py создайте каталог management, в нём каталог commands, а в нём два файла - management/commands/__init__.py (оставьте его пустым, он просто по традиции объявляет Python-пакет) и management/commands/parse.py, где parse будет названием команды.

Код файла parse.py может выглядеть примерно так (замените вашеприложение на название Django-приложения, в котором находятся модели):

from django.core.management.base import BaseCommand

from main.parser import get_array_info
from вашеприложение.models import Currency


class Command(BaseCommand):
    help = 'Runs the parser'

    def add_arguments(self, parser):
        parser.add_argument('path_to_file', help='Path to file')

    def handle(self, *args, **options):
        # Django вызовет эту функцию в правильный момент после своей инициализации
        path_to_file = options['path_to_file']
        data = get_array_info(path_to_file)  # Парсер запускается здесь
        for el in data:
            # Модели используются здесь
            el_to_db = Currency(date = el['date'], unit=el['unit'], rate=el['rate'])
            el_to_db.save()

Теперь можно запустить команду и указать путь к файлу:

python manage.py parse "C:\Users\Никита\Desktop\Django\Курс валют\venv\app\array.txt"

Django вызовет функцию add_arguments, в которой команда объявляет, что она готова принять один аргумент командной строки, а после полной инициализации вызовет функцию handle, в которой options['path_to_file'] вытаскивает аргумент по его названию, а дальше можно делать что угодно.

Подробнее в документации: https://docs.djangoproject.com/en/5.2/howto/custom-management-commands/


Весь код, связанный с urlopen, inner_soup и 'array.txt', тоже нужно завернуть в функцию и запускать через management-команду, которую можно назвать, например, download_and_save, а вместо 'array.txt' получать название сохраняемого файла из аргумента командной строки. Но чтобы не удлиннять и без того длинный ответ, я не стану это делать и оставлю в качестве домашнего задания автору вопроса.

Вернуться на верх