Django Pytest Test URL Based on Settings - python

I have an endpoint /docs in django that I only want to be visible when DEBUG = True in settings - otherwise, it should throw a 404. My setup looks like this
urls.py
urlpatterns = ...
if settings.DEBUG:
urlpatterns += [
url(r'^docs/$', SwaggerSchemaView.as_view(), name='api_docs'),
]
When doing testing, though, django doesn't automatically reload urls.py, which means simply overriding DEBUG to True or False doesn't work.
My tests look something like this
#override_settings(DEBUG=True)
#override_settings(ROOT_URLCONF='config.urls')
class APIDocsTestWithDebug(APITestCase):
# check for 200s
...
#override_settings(DEBUG=False)
#override_settings(ROOT_URLCONF='config.urls')
class APIDocsTestWithoutDebug(APITestCase):
# check for 404s
...
Now here's the weird part: When I run the tests individually using pytest path/to/test.py::APIDocsTestWithDebug and pytest path/to/test.py::APIDocsTestWithoutDebug, both tests pass. However, if I run the test file as a whole (pytest path/to/test.py), APIDocsTestWithDebug always fails. The fact that they work individually but not together tells me that the url override is working, but when the tests are in tandem, there is some bug that messes things up. I was wondering if anybody had come across a similar issue and either has an entirely different solution or can give me some tips as to what I'm doing wrong.

I struggled with the same issue. The thing is that Django loads your urlpatterns once while initializing - and overriding the settings with the decorator doesn't change what was initially loaded.
Here's what worked for me - try reloading your urls module (based on this) and clearing url caches with clear_url_caches() before the failing test cases:
import sys
from importlib import reload, import_module
from django.conf import settings
from django.core.urlresolvers import clear_url_caches # Or -> from django.urls import clear_url_caches
def reload_urlconf(urlconf=None):
clear_url_caches()
if urlconf is None:
urlconf = settings.ROOT_URLCONF
if urlconf in sys.modules:
reload(sys.modules[urlconf])
else:
import_module(urlconf)
PS: You might also want to restore the urlpatterns later - just run reload_urlconf within other settings.

You can use #pytest.mark.urls: https://pytest-django.readthedocs.io/en/latest/helpers.html#pytest.mark.urls
#pytest.mark.urls('myapp.test_urls')
def test_something(client):
assert 'Success!' in client.get('/some_url_defined_in_test_urls/').content
You could even define the URLs within the same file:
def some_view(request):
return HttpResponse(b"Success!")
urlpatterns = [
path("some-url/", some_view)
]
#pytest.mark.urls(__name__)
def test_something(client):
assert b'Success!' in client.get('/some-url/').content

Related

Value of settings.DEBUG changing between settings and url in Django Test

I'm trying to set up test for some URLS that are set only in debug. They are not set because apparently the value of DEBUG change to False between my setting file and urls.py. I've never encountered this problem before, and I don't remember doing anything particularly fancy involving DEBUG value.
Here's my urls.py :
from django.conf import settings
from my_views import dnfp
print "settings.DEBUG in url: {}".format(settings.DEBUG)
if settings.DEBUG:
urlpatterns += [url(r'^dnfp/$', dnfp, name="debug_not_found_page"...
Here's my setting file :
DEBUG=True
print "DEBUG at the end of the settings: {}".format(DEBUG)
The content that fail in my test :
reverse("debug_not_found_page"),
Here's the output of the test :
DEBUG at the end of the settings: True
settings.DEBUG in url: False
Creating test database for alias 'default'...
.E
(...)
NoReverseMatch: Reverse for 'debug_not_found_page' with arguments '()' and keyword arguments '{}' not found. 0 pattern(s) tried: []
If I change the value myself in urls.py the url is set again and the test works with this urls.py :
from django.conf import settings
from my_views import dnfp
settings.DEBUG = True
if settings.DEBUG:
urlpatterns += [url(r'^dnfp/$', dnfp, name="debug_not_found_page"...
Any ideas when and why my value for DEBUG is changing between settings and urls ?
From the docs
Regardless of the value of the DEBUG setting in your configuration file, all Django tests run with DEBUG=False. This is to ensure that the observed output of your code matches what will be seen in a production setting.
The problem with your code is you are setting DEBUG = True after this line
urlpatterns += [url(r'^dnfp/$', dnfp, name="debug_not_found_page"
The reason is that all the URLs are already appended to urlpatterns[] and you are setting it after the appending of URLs and while appending the URL Django actually transfer control to urls.py for syntax validation purpose. That's why you are getting different value in urls.py.
Set value of DEBUG before this line
Try this, hope it will work.
You can use another approach for doing this, create a separate app for all these type of URLs and don't add the app to INSTALLED_APPS on the basis of debug variable.

Where to implement python classes in Django?

I'm learning Django on my own and I can't seem to get a clue of where I implement a regular Python class. What I mean is, I don't know where do the Python classes I write go. Like they go in a separate file and then are imported to the views.py or are the classes implemented inside the views.py file?
Example I want to implement a Class Alphabet, should I do this in a separate file inside the module or just implement the functions inside the views.py file?
I don't know where do the Python classes I write go. Like they go in
a separate file and then are imported to the views.py.
Example I want to implement a Class Alphabet.
It's just a matter of getting your import statement correct:
django_proj1/
django_proj1/
myapp/
myclasses.py
views.py
Then in your view:
#myapp/views.py:
from myapp.myclasses import Alphabet
Or, you could do it like this:
django_proj1/
django_proj1/
myclasses.py
myapp/
views.py
And in your view:
#myapp/views.py:
from django_proj1.myclasses import Alphabet
Response to comment:
And after I successfully imported my class, how do I pass the
attributes to an HTML template?
The following is straight from the official django tutorial.
myapp/views.py:
from django.shortcuts import render
from django.http import HttpResponse
from myapp.myclasses import Alphabet #<*** Import your class.
from django.template import RequestContext, loader #<*** Import stuff for the template.
# Create your views here.
def index(request):
alph = Alphabet()
result = alph.change('hello') #Your class produces some result here.
template = loader.get_template("myapp/index.html")
context = RequestContext(request, {
'x' : result #<*** Make a variable named x available in your template.
})
return HttpResponse(template.render(context))
The directory structure looks like this:
django_proj1/
django_proj1/
myapp/
myclasses.py
views.py
templates/ <***Create this directory
myapp/ <***Create this directory
index.html <***Create this file
myapp/templates/myapp/index.html:
{% if x %}
<div>The result you requested was: {{x}}</div>
{% else %}
<div>Sorry, couldn't get the result.</div>
{% endif %}
myapp/myclasses.py:
class Alphabet:
def change(self, word):
return word + 'Z'
Start the server:
.../my_django_projects/django_proj1$ python manage.py runserver
url in your browser:
http://localhost:8000/myapp/
You should see:
The result you requested was: helloZ
If you comment out the following line in myapp/views.py:
context = RequestContext(request, {
#'x' : result #<*** Make a variable named x available in your template.
})
Then the template will send the else portion of index.html to the browser:
Sorry, couldn't get the result.
django_proj1/django_proj1/urls.py:
from django.conf.urls import patterns, include, url
from django.contrib import admin
from . import views
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'dj1.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),
url(r'^admin/', include(admin.site.urls)),
url(r'^myapp/', include('myapp.urls')),
)
django_proj1/myapp/urls.py:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
]
Django is just Python at the end of the day.
You can create new modules anywhere in your project, and import them into your views, models, urls, etc. This is often how you'd organize general utils (utils.py).
You can deliver data to your views in a few ways, for instance:
from your_module import some_object
class MyView(TemplateView):
template_name = 'some_template.html'
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
context['my_data'] = some_object.some_method()
return context
And in some_template.html: {{ my_data }}
It depends on the scope of the Alphabet class. If it is a utility class then I would suggest to put in a utils.py file, for example. But it is perfectly fine to have classes in the views.py file, mainly those dealing with UI processing. Up to you.
Distinct to similar frameworks, you can put your Python code anywhere in your project, provided you can reference them later by their import path (model classes are partially an exception, though):
Applications are referenced by their import path (or an AppConfig import path). Although there's some magic involving test.py and models.py, most of the times the import / reference is quite explicit.
Views are referenced by urls.py files, but imported as regular python import path.
Middlewares are referenced by strings which denote an import path ending with their class name.
Other settings you normally don't configure are also full import paths.
The exception to this explicitness is:
models.py, test.py, admin.py : They have special purposes and may not exist, providing:
You will not need any model in your app, and will provide an AppConfig (instead of just the app name) in your INSTALLED_APPS.
You will not rely on autodiscovery for admin classes in your app.
You don't want to make tests on your app, or will specify a non-default path for your app-specific test command run.
templates and static files: your project will rely on per-app loaders for your static files and for your templates files, and ultimately there's a brute-force search in each of your apps: their inner static/ and templates/ directories, if exist, are searched for those files.
Everything else is just normal python code and, if you need to import them from any view, you just do a normal import sentence for them (since view code is imported with the normal Python import mechanism).

Reverse not found for my custom model admin urls after reload(sys.modules['urls.py'])

After doing following when I call reverse for my custom model admin url its giving me Reverse not found and before reloading urls.py reverse working fine.
def _reset_urls(self, urlconf_modules):
"""Reset `urls.py` for a set of Django apps."""
for urlconf in urlconf_modules:
if urlconf in sys.modules:
reload(sys.modules[urlconf])
clear_url_caches()
resolve('/')
I debugged this and find out that admin.site._registry is empty when I call reload(sys.modules[urlconf]) because it creates new AdminSite object.
I tried preserving admin.site in a variable before reload(sys.modules[urlconf]) and assigning it back to admin.site after reload but it didn't work.
Need help.
Thanks in advance.
I was running into the same issue running Django 1.7, this seems to fix it for me:
import sys
from importlib import reload # Python 3
from django.conf import settings
from django.core.urlresolvers import clear_url_caches
from django.utils.importlib import import_module
def reload_urlconf(urlconf=None):
clear_url_caches()
if urlconf is None:
urlconf = settings.ROOT_URLCONF
if urlconf in sys.modules:
reload(sys.modules[urlconf])
import_module(urlconf)

Module imported twice cause recreation on an object inside it

I have a Django project. A part from this Django project was a reporting module that searches for reports directory inside all INSTALLED_APPS, very similar to autodiscover mechanism of the admin interface.
This module had a small registery class that registers classes found. In a very simplified way it looks something like this:
def autodiscover():
"""
Searches for reports module in all INSTALLED_APPS
"""
global REPORTSLOADING
if REPORTSLOADING:
return
REPORTSLOADING = True
import imp
from django.conf import settings
for app in settings.INSTALLED_APPS:
try:
app_path = import_module(app).__path__
except AttributeError:
continue
try:
imp.find_module('reports', app_path)
except ImportError:
continue
import_module("%s.reports" % app)
REPORTSLOADING = False
class ReportsRegistery(object):
.....
registery = ReportsRegistery()
If any of the INSTALLED_APPS need to register a Report class, we need a line inside reports/__init__.py:
import reports
reports.registery.register(SomeReportClass)
And inside the main urls.py i would do:
import reports
reports.autodiscover()
urlpatterns = patterns('',
....
(r'', include(reports.registery.urls)),
)
Now I decided to create a pluggable django application for it and placed the same code in __init__.py of the package. The problem I am facing is that reports module with the new structure gets imported twice, thus causing the recreation of the 'registery' object. So, no urls are actually registered. It's loaded one time from the import inside urls.py (as expected) and another one initiated by autodiscover. I have verified this by:
print hex(id(registery))
and found out it returned 2 different values.
I thought that the reports package will be imported once just like when it was just a module.
How can I prevent it from being loaded twice ? Or how can I ensure that we will have only one ReportsRegistery instance to work with ?
It's not uncommon for Django to import modules twice. There are two reasons for this:
The classic Django project layout encouraged you to have your working directory on the path twice at two different locations. This meant you could import something as project.module, or as app.project.module, which would confuse the import machinery.
The settings.py file is actually imported twice.
Fixes:
Double-check that all of your imports use the same style of path.
Don't import your module from settings.py

Why isn't admin.autodiscover() called automatically in Django when using the admin, why was it designed to be called explicitly?

Without putting admin.autodiscover() in urls.py the admin page shows You don't have permission to edit anything (See SO thread).
Why is this so? If you always need to add admin.autodiscover() to edit information using the admin even though you have a superuser name and password for security why didn't the Django developers trigger admin.autodiscover() automatically?.
Before Django 1.7, the recommendation was to put the admin.autodiscover() call in urls.py. That allowed it to be disabled if necessary. Requiring admin.autodiscover() instead of calling it automatically was an example of the Python philosophy 'Explicit is better than implicit' in action. Remember, the django.contrib.admin app is optional, it is not installed on every site, so it wouldn't make sense to always run autodiscover.
Most of the time autodiscover works well enough. However if you require more control, you can manually import specific apps' admin files instead. For example, you might want to register multiple admin sites with different apps in each.
App loading was refactored in Django 1.7. The autodiscover() was moved to the admin app's default app config. That means that autodiscover now runs when the admin app is loaded, and there's no need to add admin.autodiscover() to your urls.py. If you do not want autodiscovery, you can now disable it by using the SimpleAdminConfig instead.
(edit: Obsoleted after Django 1.7+, not necessary more, see Alasdair's answer)
I think it's about giving you finer control. Consider the code of contrib.admin.autodiscover:
def autodiscover():
"""
Auto-discover INSTALLED_APPS admin.py modules and fail silently when
not present. This forces an import on them to register any admin bits they
may want.
"""
import copy
from django.conf import settings
from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule
for app in settings.INSTALLED_APPS:
mod = import_module(app)
# Attempt to import the app's admin module.
try:
before_import_registry = copy.copy(site._registry)
import_module('%s.admin' % app)
except:
# Reset the model registry to the state before the last import as
# this import will have to reoccur on the next request and this
# could raise NotRegistered and AlreadyRegistered exceptions
# (see #8245).
site._registry = before_import_registry
# Decide whether to bubble up this error. If the app just
# doesn't have an admin module, we can ignore the error
# attempting to import it, otherwise we want it to bubble up.
if module_has_submodule(mod, 'admin'):
raise
So it will automatically load the INSTALLED_APPS admin.py modules and fail silently when not found. Now, there are cases when you actually don't want that such as when using your own AdminSite:
# urls.py
from django.conf.urls import patterns, url, include
from myproject.admin import admin_site
urlpatterns = patterns('',
(r'^myadmin/', include(admin_site.urls)),
)
in this case, you don't need to call autodiscovery().
There are also other times when you only want to see or edit a subset of apps of your projects through admin, and calling autodiscovery() would not enable you to do that.
Django doesn't require you to use django.contrib.admin on every site - it's not a core module.

Categories

Resources