Tempat yang tepat untuk menyimpan berkas saya signal.py dalam proyek Django

88

Berdasarkan dokumentasi Django yang saya baca, nampaknya signals.pydalam folder app adalah tempat yang baik untuk memulai, tetapi masalah yang saya hadapi adalah ketika saya membuat sinyal untuk pre_savedan saya mencoba untuk mengimpor kelas dari model itu bertentangan dengan importdalam model saya.

# 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]

Kode ini tidak akan berjalan karena saya mengimpor Comm_Queuedi dalam signals.pydan saya juga mengimpor sinyal di dalamnya models.py.

Adakah yang bisa memberi nasihat tentang bagaimana saya bisa mengatasi masalah ini?

Salam

Mo J. Mughrabi
sumber

Jawaban:

65

Jawaban asli, untuk Django <1.7:

Anda dapat mendaftarkan sinyal dengan mengimpor signals.pydi file aplikasi __init__.py:

# __init__.py
import signals

Ini akan memungkinkan untuk mengimpor models.pydari signals.pytanpa kesalahan impor melingkar.

Satu masalah dengan pendekatan ini adalah bahwa ini mengacaukan hasil cakupan jika Anda menggunakan coverage.py.

Diskusi terkait

Edit: Untuk Django> = 1.7:

Sejak AppConfig diperkenalkan, cara yang disarankan untuk mengimpor sinyal adalah dalam init()fungsinya. Lihat jawaban Eric Marcos untuk lebih jelasnya.

yprez
sumber
6
menggunakan sinyal dalam Django 1.9, gunakan metode di bawah ini (direkomendasikan oleh django). metode ini tidak berhasil memberiAppRegistryNotReady("Apps aren't loaded yet.")
s0nskar
1
Eric Marcos jawabannya haruslah jawaban yang diterima: stackoverflow.com/a/21612050/3202958 sejak Django> = 1.7, menggunakan konfigurasi aplikasi
Nrzonline
1
Sepakat. Saya akan mengedit jawaban untuk menunjuk ke jawaban Eric Marcos untuk Django 1.7+
yprez
197

Jika Anda menggunakan Django <= 1.6 Saya akan merekomendasikan solusi Kamagatos: cukup impor sinyal Anda di akhir modul model Anda.

Untuk versi Django (> = 1.7) yang akan datang, cara yang disarankan adalah dengan mengimpor modul sinyal Anda dalam fungsi config ready () aplikasi Anda :

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
sumber
7
Mereka juga menyebutkan dalam dokumentasi 1.7 bahwa terkadang siap dapat dipanggil beberapa kali dan untuk menghindari sinyal duplikat, lampirkan pengenal unik ke panggilan konektor sinyal Anda: request_finished.connect (my_callback, dispatch_uid = "my_unique_identifier") Di mana dispatch_uid biasanya berupa string tetapi bisa berupa objek hashable apa pun. docs.djangoproject.com/en/1.7/topics/signals/…
Emeka
13
Ini harus menjadi jawaban yang diterima! Jawaban yang diterima di atas menimbulkan kesalahan saat menerapkan menggunakan uwsgi
Patrick
2
Hm, tidak bekerja untuk saya dengan django 2. Jika saya mengimpor model langsung dalam keadaan siap - semuanya baik-baik saja. Jika saya mengimpor model dalam sinyal dan mengimpor sinyal siap, saya mendapatkan kesalahan doesn't declare an explicit app_label..
Aldarund
@Aldarun Anda dapat mencoba untuk meletakkan 'my_app.apps.MyAppConfig' di dalam INSTALLED_APPS.
Ramil Aglyautdinov
26

Untuk mengatasi masalah Anda, Anda hanya perlu mengimpor signal.py setelah definisi model Anda. Itu saja.

Kamagatos
sumber
2
Sejauh ini ini adalah yang termudah, dan saya tidak tahu ini akan berhasil tanpa ketergantungan siklik. Terima kasih!
bradenm
2
Cemerlang. Seperti yang ini lebih baik dari jawaban saya. Meskipun saya tidak terlalu mengerti kenapa tidak menyebabkan impor melingkar ...
yprez
solusi tidak berfungsi dengan plugin autopep8 yang diaktifkan di Eclipse.
ramusus
5

Saya juga menempatkan sinyal di file signal.py dan juga memiliki cuplikan kode ini yang memuat semua sinyal:

# 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)))

Ini untuk proyek, saya tidak yakin apakah ini berfungsi di tingkat aplikasi.

aisbaa
sumber
Ini adalah solusi favorit saya sejauh itu sesuai dengan pola lain (seperti task.py)
dalore
1
Menemukan masalah dengan yang satu ini, jika Anda memulai shell urls.py tidak dapat diimpor dan sinyal Anda tidak akan terpasang
dalore
ya, jawaban saya agak ketinggalan jaman, sepertinya django memiliki kelas AppConfig hari ini. Terakhir kali saya menggunakan django adalah versi 1.3. Menyarankan untuk menyelidiki di sekitarnya.
aisbaa
1
kami masih 1,6 dan jadi saya harus memindahkan semua signal.py kami ke model jika tidak seledri dan perintah manajemen tidak diambil
dalore
5

Dalam versi Django lama akan baik-baik saja untuk meletakkan sinyal di __init__.pyatau mungkin dimodels.py (walaupun pada model akhir akan menjadi cara yang besar untuk selera saya).

Dengan Django 1.9, lebih baik menurut saya, untuk menempatkan sinyal pada sebuah signals.pyberkas dan mengimpornya denganapps.py , di mana mereka akan dimuat setelah memuat model.

apps.py:

from django.apps import AppConfig


class PollsConfig(AppConfig):
    name = 'polls'

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

Anda juga dapat membagi sinyal Anda di signals.pydan handlers.pydi folder lain dalam model Anda yang dinamai signalsjuga, tetapi bagi saya itu hanya sekedar rekayasa. Lihatlah Sinyal Penempatan

Tyson Rodez
sumber
3

Saya menduga Anda melakukannya sehingga sinyal Anda terdaftar, sehingga mereka ditemukan di suatu tempat. Saya hanya meletakkan sinyal saya di file models.py seperti biasa.

Issac Kelly
sumber
ya ketika saya memindahkan sinyal di dalam file model itu memecahkan masalah. Tetapi file model.py saya cukup besar dengan semua kelas, manajer dan aturan model.
Mo J. Mughrabi
1
Manajer agak lebih mudah menarik pengalaman saya. Managers.py ftw.
Issac Kelly
3

Ini hanya berlaku jika Anda memiliki sinyal di tempat terpisah signals.py file

Sepenuhnya setuju dengan jawaban @EricMarcos tetapi harus dinyatakan bahwa dokumen django secara eksplisit menyarankan untuk tidak menggunakan variabel default_app_config (meskipun tidak salah). Untuk versi saat ini, cara yang benar adalah:

my_app / apps.py

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        import my_app.signals

settings.py

(Pastikan Anda tidak hanya memiliki nama aplikasi Anda di aplikasi yang diinstal tetapi juga jalur relatif ke AppConfig Anda)

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

Alternatifnya adalah mengimpor fungsi panggilan balik dari signals.pydan menghubungkannyamodels.py :

signal.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: Mengimpor YourModeldi signals.pyakan membuat rekursi; menggunakansender , sebagai gantinya.

Ps2: Menyimpan instance lagi dalam fungsi callback akan membuat rekursi. Anda dapat membuat argumen kontrol dalam .savemetode untuk mengontrolnya.

Rafael
sumber