模块未找到错误:没有名为“ ”的模块

-4 投票
1 回答
94 浏览
提问于 2025-04-14 16:57

这里是背景信息:

我正在使用Django框架进行开发。我开始编写一些功能,但停下来设置第一个单元测试。

我想测试我在JoBooking.models.py文件中的CustomUser类,这个JoBooking是我在Django项目中创建的一个应用。

于是我在测试文件test_models.py中导入了JoParis.JoBookings.models里的CustomUser。

当我在终端运行python manage.py test或pytest时,遇到了这个错误:ModuleNotFoundError: No module named 'JoParis.JoBooking'。

我已经在settings.py中配置了这个应用(JoBooking),但是还是不行。

包里有init.py文件。

我做了一个简单的测试,检查我的应用的URL是否返回HTTP 200响应,测试是成功的,但我必须去掉从JoParis.JoBookings.models导入CustomUser的那一行。所以测试文件能找到,但当我想测试我的模型时,导入就出问题了。

我卡住了。我不明白这个错误。ModuleNotFoundError: No module named。请给我解释一下这个错误是什么。谢谢。

在test_models.py中

from django.test import TestCase
from ...JoBooking.models import CustomUser


# test inscription avec données valides et obligatoires
class TestCreateUser(TestCase):
    def test_create_user(self):
        user = CustomUser.objects.create_user(
            email='usertest@mail.com',
            password='password',
            username='',
            first_name='user',
            last_name='test',
            is_superuser=False,
            is_staff=False
        )
        assert CustomUser.objects.filter(email='usertest@mail.com').exists()  # verifie si dans la BDD
        assert user.first_name == 'user'
        assert user.last_name == 'test'
        assert user.is_superuser is False  # verifie si attribut par défaut pris en compte
        assert user.is_staff is False


# test inscription  avec données non valides
# test connexion user avec True data
# test connexion user avec False data


# test qui verifie  http response = 200
class TestUrl(TestCase):
    def test_my_view(self):
        response = self.client.get('http://127.0.0.1:8000/')
        self.assertEqual(response.status_code, 200)

在settings.py中

from pathlib import Path
import os

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = x

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'JoBooking',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'JoParis.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, "JoBooking/templates/JoBooking")],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'JoParis.wsgi.application'

# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'OPTIONS': {
            "read_default_file": 'C:\\Users\\murhe\\OneDrive\\Bureau\\EXAM JO\\my.cnf.txt',
        },
    }
}

# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/5.0/topics/i18n/

LANGUAGE_CODE = 'fr-fr'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/

STATIC_URL = '/static/'

# Default primary key field type
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

AUTH_USER_MODEL = 'JoBooking.CustomUser'

AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.ModelBackend',
    'JoBooking.authbackends.EmailAuthBackend',
]

错误信息


: (venv) PS C:\Users\murhe\PycharmProjects\StudiProject\JoParis> pytest
====================================================================== test session starts =======================================================================
platform win32 -- Python 3.11.1, pytest-8.0.1, pluggy-1.4.0
django: version: 5.0.2, settings: JoParis.settings (from ini)
rootdir: C:\Users\murhe\PycharmProjects\StudiProject\JoParis
configfile: pytest.ini
plugins: django-4.8.0
collected 0 items / 2 errors                                                                                                                                      

============================================================================= ERRORS ============================================================================= 
________________________________________________________ ERROR collecting JoBooking/tests/test_models.py _________________________________________________________ 
ImportError while importing test module 'C:\Users\murhe\PycharmProjects\StudiProject\JoParis\JoBooking\tests\test_models.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
..\..\..\AppData\Local\Programs\Python\Python311\Lib\importlib\__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
E   ModuleNotFoundError: No module named 'JoParis.JoBooking'
_________________________________________________________ ERROR collecting JoBooking/tests/test_views.py _________________________________________________________ 
ImportError while importing test module 'C:\Users\murhe\PycharmProjects\StudiProject\JoParis\JoBooking\tests\test_views.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
..\..\..\AppData\Local\Programs\Python\Python311\Lib\importlib\__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
E   ModuleNotFoundError: No module named 'JoParis.JoBooking'
======================================================================== warnings summary ======================================================================== 
..\venv\Lib\site-packages\_pytest\config\__init__.py:1396
  C:\Users\murhe\PycharmProjects\StudiProject\venv\Lib\site-packages\_pytest\config\__init__.py:1396: PytestConfigWarning: Unknown config option: PYTHONPATH       

    self._warn_or_fail_if_strict(f"Unknown config option: {key}\n")

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
==================================================================== short test summary info ===================================================================== 
ERROR JoBooking/tests/test_models.py
ERROR JoBooking/tests/test_views.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 2 errors during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
================================================================== 1 warning, 2 errors in 0.68s ===============

在views.py中

from django.shortcuts import render, redirect
from django.contrib.auth import login, logout
from .forms import Connexion
from django.contrib.auth.forms import UserCreationForm
from .models import CustomUser, Offre, Reservation, Commande
from .authbackends import EmailAuthBackend
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404
import uuid

# méthode pour créer un formulaire d'inscription (utilisation du formulaire émit par django par défaut)
class CustomSignupForm(UserCreationForm):
    class Meta:
        model = CustomUser  # ici on spécifie qu'on veut ce modèle personnalisé
        fields = ('first_name', 'last_name', 'email')
        #note : le password est automatiquement intégré dans ce form


# view pour la page d'accueil
def index(request):
    return render(request, 'JoBooking/index.html')


# view pour la page des offres
def offres(request):
    list_offres = Offre.objects.all()  # affiche toutes les offres  = plan solo,duo,family ou autre
    context = {'offres': list_offres}
    return render(request, 'JoBooking/offres.html', context)


# view pour la page d'inscription
def inscription(request):
    context = {}  # stockage données  lors du rendu de page

    if request.method == 'POST':  # vérification methode de la requête est POST = formulaire soumis
        form = CustomSignupForm(request.POST)  # création formulaire avec données POST
        if form.is_valid():  # vérification  de la validité des données
            user = form.save()  # sauvegarde dans la BDD
            user.cle_inscription = uuid.uuid4().hex
            user.save()

            return redirect('inscription_reussie')  # redirection de l'utilisateur vers une autre page
        else:
            context['errors'] = form.errors  # erreur de validation

    form = CustomSignupForm()  # nouvelle page d'inscription vide  donc sans POST
    context['form'] = form
    return render(request, 'JoBooking/inscription.html', context=context)  # renvoie page d'inscription


# view pour l'inscripion réussie d'un user
def inscription_reussie(request):
    return render(request, 'inscription_reussie.html')


# view pour la page de connexion
def connexion(request):
    message = ""
    if request.method == 'POST':
        form = Connexion(request.POST)  # création formulaire avec données envoyées via POST
        if form.is_valid():  # vérification de la validité des données
            email = form.cleaned_data['email']  # données nettoyées et récupérées
            password = form.cleaned_data['password']

            custom_auth = EmailAuthBackend()  # implémentation de l'authentification personnalisée
            user = custom_auth.authenticate(request, email=email, password=password)  # appel de la méthode authenticate

            # email = request.POST.get('email')
            # password = request.POST.get('password')

            if user is not None and password is not None:  # signification : si l'user a été trouvée ...
                login(request, user, backend='JoBooking.authbackends.EmailAuthBackend')  # ya 2 backends d'auth
                message = f'Bienvenue {user.first_name} ! Vous êtes connecté.'
                return redirect('/')  # redirige vers la page d'acceuil
            else:
                message = 'Identifiants non valides.'
                return render(request, 'connexion.html',
                              context={'message': message})  # user non trouvé, donc retourne page connexion
    else:
        form = Connexion()

    return render(request, 'connexion.html', context={'form': form, 'message': message})


# méthode pour que les users se déconnecte
def deconnexion(request):
    logout(request)
    return redirect('index')


# méthode pour ajouter à réservation. ( réservation c'est le panier. J'ai volontairement choisi ce terme)
def ajouter_reservation(request, offre_id):
    user = request.user
    offre = get_object_or_404(Offre, id=offre_id)  # ici on récupère l'offre si inexistante,erreur 404
    reservation, _ = Reservation.objects.get_or_create(user=user)  # récupération du panier
    commande, created = Commande.objects.get_or_create(user=user,
                                                       offre=offre)  # récupération commande

    if created:  # exemple:  une offre n'est pas dans la commande donc elle sera crée
        reservation.commandes.add(commande)
        reservation.save()
    else:  # l'offre est deja dans la commande donc on augmente la quantité
        commande.quantity += 1
        commande.save()
    return redirect('offres')


#  renvoie  à la page de reservation (c'est la page panier, après avoir réserver)
@login_required(login_url='connexion')  # connexion nécessaire pour avoir accès a cette page
def reservation(request):
    reservation = get_object_or_404(Reservation, user=request.user)
    return render(request, 'reservation.html', context={
        'commandes': reservation.commandes.all()})  # affiche tous les éléments qu'ya dans la réservation


# methode pour annuler une réservation au complet
def annulation(request):
    user_reservation = Reservation.objects.get(user=request.user)
    if user_reservation:  # si elle existe
        user_reservation.commandes.all().delete()
        user_reservation.delete()  # suppression de tout ce qu'y a dans la réservation qu'on supprime ensuite

    return redirect('index')  # retourne vers la page d'accueil


def payer(request):
    reservation = request.user.reservation

    # génération de billets (combinaison des deux clés générées , qr code, nom acheteur + logo ;date de l'evement )

    # augmentation de ventes de Offre selon la quantité achetée
    for commande in reservation.commandes.all():
        offre = commande.offre  # récupration du plan dans la commande
        offre.ventes += commande.quantity
        offre.save()
    # paiement dans Reservation devient TRUE (initialement à FALSE)
    reservation.paiement = True
    reservation.save()

    # génération de clé unique pour paiment seulement si payer.
    if reservation.paiement == True:
        cle_paiement = uuid.uuid4().hex
        reservation.cle_paiement = cle_paiement
        reservation.save()

    # le panier (réservation ) est réinitialisé
    reservation.commandes.clear()
    # Renvoie à la page de remerciement où on peut telecharger billet
    return redirect('remerciements')


def remerciements(request):
    return render(request, 'remerciements.html')

'''

在test_views.py中

'''

from django.test import TestCase
from ...JoBooking.models import CustomUser


class TestUrl(TestCase):
    def test_my_view(self):
        response = self.client.get('http://127.0.0.1:8000/')
        self.assertEqual(response.status_code, 200)

'''

另一种测试方法

(venv) PS C:\Users\murhe\PycharmProjects\StudiProject\JoParis> python manage.py test JoBooking
Found 1 test(s).
System check identified no issues (0 silenced).
E
======================================================================
ERROR: JoParis.JoBooking (unittest.loader._FailedTest.JoParis.JoBooking)
----------------------------------------------------------------------
ImportError: Failed to import test module: JoParis.JoBooking
Traceback (most recent call last):
  File "C:\Users\murhe\AppData\Local\Programs\Python\Python311\Lib\unittest\loader.py", line 440, in _find_test_path
    package = self._get_module_from_name(name)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\murhe\AppData\Local\Programs\Python\Python311\Lib\unittest\loader.py", line 350, in _get_module_from_name
    __import__(name)
ModuleNotFoundError: No module named 'JoParis.JoBooking'

1 个回答

0

一般回答:

可以试试下面的方式。

from JoBookings.models import CustomUser 

你应该使用相对路径来导入其他脚本。

JoParis.JoBookings.models 中:

JoParis 是文件夹名/项目名。

JoBookings 是应用名/应用文件夹名(在项目文件夹内)。

models 是 Python 文件名(在应用文件夹内)。

这些都是相对于当前(导入语句所在的)文件的路径。

所以,要像上面那样导入,JoParis 应该是当前测试脚本的子文件夹。


关于问题的新信息:

对于 C:\Users\murhe\PycharmProjects\StudiProject\JoParis\JoBooking\tests\test_models.py 的位置,可以试试下面的方式。

from ...JoBookings.models import CustomUser 

更多信息:

链接 1

链接 2


Django 测试:

查看这里了解如何运行测试

撰写回答