O lugar certo para manter meu arquivo signs.py em um projeto Django

88

Com base na documentação do Django que eu estava lendo, parece que signals.pya pasta do app é um bom lugar para começar, mas o problema que estou enfrentando é que quando eu crio sinais para pre_savee tento importar a classe do modelo, ela entra em conflito com o importno meu modelo.

# models.py

from django.contrib.auth.models import User
from django.db import models
from django.utils.translation import gettext as _
from signals import *

class Comm_Queue(CommunicatorAbstract):
    queue_statuses = (
        ('P', _('Pending')),
        ('S', _('Sent')),
        ('E', _('Error')),
        ('R', _('Rejected')),
    )
    status          = models.CharField(max_length=10, db_index=True, default='P')
    is_html         = models.BooleanField(default=False)
    language        = models.CharField(max_length=6, choices=settings.LANGUAGES)
    sender_email    = models.EmailField()
    recipient_email = models.EmailField()
    subject         = models.CharField(max_length=100)
    content         = models.TextField()

# signals.py

from django.conf import settings
from django.db.models.signals import pre_save
from django.dispatch import receiver
from models import Comm_Queue

@receiver(pre_save, sender=Comm_Queue)
def get_sender_email_from_settings(sender, **kwargs):
    obj=kwargs['instance']
    if not obj.sender_email:
        obj.sender_email='%s' % settings.ADMINS[0][1]

Este código não será executado porque eu importo Comm_Queuedentro signals.pye também importo os sinais dentro models.py.

Alguém pode me aconselhar sobre como eu poderia superar esse problema?

Saudações

Mo J. Mughrabi
fonte

Respostas:

65

Resposta original, para Django <1.7:

Você pode registrar os sinais importando signals.pyno __init__.pyarquivo do aplicativo :

# __init__.py
import signals

Isso permitirá a importação models.pyde signals.pysem erros de importação circulares.

Um problema com essa abordagem é que ela bagunça os resultados da cobertura se você estiver usando o cover.py.

Discussão relacionada

Editar: Para Django> = 1,7:

Desde que o AppConfig foi introduzido, a forma recomendada de importar sinais está em sua init()função. Veja a resposta de Eric Marcos para mais detalhes.

Yprez
fonte
6
usando sinais no Django 1.9, use o método abaixo (recomendado pelo django). este método não funciona dandoAppRegistryNotReady("Apps aren't loaded yet.")
s0nskar
1
Eric Marcos sua resposta deve ser a resposta aceita: stackoverflow.com/a/21612050/3202958 já que Django> = 1.7, usando a configuração do aplicativo
Nrzonline 01 de
1
Acordado. Vou editar a resposta para apontar para a resposta de Eric Marcos para Django 1.7+
yprez
197

Se você estiver usando Django <= 1.6, eu recomendo a solução Kamagatos: basta importar seus sinais no final do módulo de seus modelos.

Para versões futuras do Django (> = 1.7), a maneira recomendada é importar seu módulo de sinais na função config ready () do seu aplicativo :

my_app/apps.py

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        import my_app.signals

my_app/__init__.py

default_app_config = 'my_app.apps.MyAppConfig'
Eric Marcos
fonte
7
Eles também mencionam na documentação 1.7 que às vezes pronto pode ser chamado várias vezes e, assim, para evitar sinais duplicados, anexe um identificador exclusivo à sua chamada de conector de sinal: request_finished.connect (my_callback, dispatch_uid = "my_unique_identifier") Onde dispatch_uid é geralmente uma string mas pode ser qualquer objeto hashable. docs.djangoproject.com/en/1.7/topics/signals/…
Emeka
13
Esta deve ser a resposta aceita! A resposta aceita acima gera um erro ao implantar usando uwsgi
Patrick
2
Hm, não funciona para mim com o django 2. Se eu importar o modelo diretamente no pronto - tudo bem. Se eu importar o modelo em sinais e importar os sinais prontos, estou recebendo um erro doesn't declare an explicit app_label.
Aldarund
@Aldarun você pode tentar colocar 'my_app.apps.MyAppConfig' dentro de INSTALLED_APPS.
Ramil Aglyautdinov
26

Para resolver o seu problema, basta importar o signs.py após a definição do seu modelo. Isso é tudo.

Kamagatos
fonte
2
Isso é de longe o mais fácil, e eu não tinha ideia de que funcionaria sem uma dependência cíclica. Obrigado!
bradenm
2
Brilhante. Como este melhor do que minha resposta. Embora eu realmente não entenda como isso não causa uma importação circular ...
yprez
solução não funciona com o plugin autopep8 habilitado no Eclipse.
ramusus
5

Eu também coloco sinais no arquivo signs.py e também tenho este snippet de código que carrega todos os sinais:

# import this in url.py file !

import logging

from importlib import import_module

from django.conf import settings

logger = logging.getLogger(__name__)

signal_modules = {}

for app in settings.INSTALLED_APPS:
    signals_module = '%s.signals' % app
    try:
        logger.debug('loading "%s" ..' % signals_module)
        signal_modules[app] = import_module(signals_module)
    except ImportError as e:
        logger.warning(
            'failed to import "%s", reason: %s' % (signals_module, str(e)))

Isso é para projeto, não tenho certeza se funciona no nível do aplicativo.

aisbaa
fonte
Esta é minha solução favorita, na medida em que se encaixa nos outros padrões (como tasks.py)
dalore
1
Encontrou um problema com este. Se você iniciar o shell, o urls.py não será importado e seus sinais não serão anexados
dalore
sim, minha resposta está meio desatualizada, parece que django tem classe AppConfig atualmente. A última vez que usei o django, era a versão 1.3. Sugerindo investigar em torno disso.
aisbaa
1
ainda somos 1.6 e então eu tive que mover todos os nossos sinais.py em modelos, caso contrário, os comandos de aipo e de gerenciamento não foram escolhidos
dalore
5

Em versões antigas do Django, seria bom colocar os sinais no __init__.pyou talvez no models.py(embora no final os modelos sejam muito grandes para o meu gosto).

Com Django 1.9, acho melhor colocar os sinais em um signals.pyarquivo e importá-los com o apps.py, onde eles serão carregados após carregar o modelo.

apps.py:

from django.apps import AppConfig


class PollsConfig(AppConfig):
    name = 'polls'

    def ready(self):
        from . import signals  # NOQA

Você também pode dividir seus sinais em signals.pye handlers.pyem outra pasta dentro do seu modelo nomeado signalstambém, mas para mim isso é apenas mais engenharia. Dê uma olhada em Colocando Sinais

Tyson Rodez
fonte
3

Suponho que você esteja fazendo isso para que seus sinais sejam registrados, de modo que sejam encontrados em algum lugar. Acabei de colocar meus sinais em um arquivo models.py normalmente.

Issac Kelly
fonte
sim, quando eu movo o sinal dentro do arquivo de modelo, ele resolve o problema. Mas meu arquivo model.py é muito grande, com todas as classes, gerenciadores e regras de modelo.
Mo J. Mughrabi
1
Os gerentes são um pouco mais fáceis de usar na minha experiência. Managers.py ftw.
Issac Kelly
3

Isso só se aplica se você tiver seus sinais em um signals.pyarquivo separado

Concordo totalmente com a resposta de @EricMarcos, mas deve ser declarado que a documentação do django aconselha explicitamente não usar a variável default_app_config (embora não esteja errada).Para as versões atuais, a maneira correta seria:

my_app / apps.py

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        import my_app.signals

settings.py

(Certifique-se de não ter apenas o nome do seu aplicativo nos aplicativos instalados, mas também o caminho relativo para o seu AppConfig)

INSTALLED_APPS = [
    'my_app.apps.MyAppConfig',
    # ...
]
Xen_mar
fonte
1

Uma alternativa é importar as funções de retorno de chamada signals.pye conectá-las emmodels.py :

sinais.py

def pre_save_callback_function(sender, instance, **kwargs):
    # Do stuff here

model.py

# Your imports here
from django.db.models.signals import pre_save
from yourapp.signals import pre_save_callback_function

class YourModel:
    # Model stuff here
pre_save.connect(pre_save_callback_function, sender=YourModel)

Ps: Importando YourModelem signals.pycriará uma recursão; usarsender , em vez disso.

Ps2: Salvar a instância novamente na função de retorno de chamada criará uma recursão. Você pode fazer um argumento de controle no .savemétodo para controlá-lo.

Rafael
fonte