Search code examples
djangodjango-viewsdjango-urlsdjango-cms

Django and DjangoCMS View Management


I am trying to understand how Django in general and DjangoCMS in particular manage views.

I have the following in project's urls.py:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('video/', include('video_uploader.urls')),
    path('user/', include('user_accounts.urls')),
    path('', include('cms.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

That is, there are a couple of specific routes and the rest is handled by DjangoCMS (cms.urls).

Inside one of the apps (corresponding to /user/ path above), I have this:

from django.urls import path

from . import views

app_name = 'user_accounts'
urlpatterns = [
    path('signup', views.user_signup, name='signup'),
]

The view for this path is as follows:

def user_signup(request):
   if request.method == 'POST':
       form = UserCreationForm(request.POST)
       print('Is the form valid?')
       print(form.is_valid())
       if form.is_valid():
           user = form.save()
           login(request, user)
           return redirect('/')
   else:
       form = UserCreationForm()
   return render(request, 'user_accounts/signup.html', {'form': form})

So far, so good. Now we get to the interesting bits.

user_accounts/signup.html

{% extends 'base.html' %}

{% block content %}
<div class="container">
    <h2>Sign up</h2>
    <form method="post" novalidate>
    {% csrf_token %}
    {% include 'includes/form.html' %}
    <button type="submit" class="btn btn-primary">Create an account</button>
    </form>
</div>
{% endblock %}

The template above extends the base.html, which is the base template for the whole project. In other words, this sign-up form gets embedded into the content block of the page to make the app cohesive.

While testing the sign-up view, I tried to do the following:

class SignUpTest(TestCase):

    def setUp(self):
        url = reverse('user_accounts:signup')
        self.response = self.client.get(url)

    def test_signup_status_code(self):
        self.assertEqual(self.response.status_code, 200)

    def test_signup_url_resolves_signup_view(self):
        view = resolve('/user/signup/')
        print(view)
        print(view.func)
        print(resolve('/video/'))
        print(resolve('/video/').func)
        self.assertIs(view.func, user_signup)

The issue is that the test fails. It fails because the two functions are different.

> FAIL: test_signup_url_resolves_signup_view
> (user_accounts.tests.SignUpTest)
> Traceback (most recent call last):   File
> "/home/user-name/sites/project-web/project/user_accounts/tests.py",
> line 20, in test_signup_url_resolves_signup_view
>     self.assertIs(view.func, user_signup) AssertionError: <function details at 0x7f0dc78bf050> is not <function user_signup at
> 0x7f0dc9891200>

What are they?

Here are the prints I have gotten.

ResolverMatch(func=cms.views.details, args=(), kwargs={'slug': 'user/signup'}, url_name=pages-details-by-slug, app_names=[], namespaces=[])`
`<function details at 0x7f0dc78bf050>
ResolverMatch(func=video_uploader.views.list_videos, args=(), kwargs={}, url_name=list_videos, app_names=['video_uploader'], namespaces=['video_uploader'])`
`<function list_videos at 0x7f0dc8ecf950>

I am using /video/ path for printing because that app has a nearly identical setup as the app in question. The major difference is that the former does not yet extend the base.html template. It appears that the view then resolves to a non-DjangoCMS view.

As you can see, the view that is actually being used when user/signup/ is visited is a DjangoCMS view. I do not understand why. Could someone tell me why? In the browser, the page looks and works just fine. However, I was expecting the view to be the one from the app, with the latter simply using a few general templates.


Solution

  • The CMS urls start here; https://github.com/divio/django-cms/blob/develop/cms/urls.py

    With the all-important bit being the details view; https://github.com/divio/django-cms/blob/develop/cms/views.py#L38

    Alongside some util functions (here) the CMS essentially tries to get a page object from the slug (url path) provided.

    Usually within a project, applications such as your video_uploader and user_accounts are connected to CMS pages via apphooks (docs).

    What you've shown is that your app template extends a template which, I assume, is setup as a CMS template, base.html. Does the base template include the CMS toolbar? I suspect you've caused conflict by extending the template because you've said that another custom view doesn't extend that same template.

    There isn't really anything wrong with hard-coding your own apps into the project with their own URLs and not using the CMS apphooks, but if you do, just keep everything isolated, make sure the templates don't pull in the CMS side of the system.

    To answer your question about URLs to apphooked pages you can use some variable assignment in templates. For example, I've got a gallery application which I can't guarantee is installed;

    {% page_url "gallery" as gallery_url %}
    

    This may return no value so it could just be set as a href that won't do anything, or you could use it in a conditional block;

    {% if gallery_url %}
        <a href="{{ gallery_url }}">
            Gallery
        </a>
    {% endif %}