meeb / django-distill

Minimal configuration static site generator for Django
MIT License
441 stars 35 forks source link

'WSGIRequest' object has no attribute 'session' #22

Closed agieocean closed 4 years ago

agieocean commented 4 years ago

Running python manage.py distill-local site-export produces the response CommandError: Failed to render view "/": 'WSGIRequest' object has no attribute 'session' URLs.py config:

from django.urls import path
from django.contrib.auth.views import LoginView, LogoutView

from . import views

from django_distill import distill_path

def get_index():
    return None
def get_ageVerification():
    return None
def get_register():
    return None
def get_login():
    return None
def get_profile():
    return None
def get_message():
    return None
def get_messages():
    return None
def get_faq():
    return None
def get_legal():
    return None

urlpatterns = [
    distill_path('', views.index, name='index', distill_func=get_index, distill_file="index.html"),
    distill_path('ageVerification', views.ageVerification, name='ageVerification', distill_func=get_ageVerification),
    distill_path('register', views.register, name="register", distill_func=get_register),
    distill_path('login', LoginView.as_view(template_name='user/login.html'), name='login', distill_func=get_login),
    path('logout', LogoutView.as_view(), name='logout'),
    distill_path('accounts/profile/', views.profile, name="profileUser", distill_func=get_profile),
    distill_path('message/<str:username>', views.message, name="message", distill_func=get_message),
    distill_path('messages', views.messages, name="messages", distill_func=get_messages),
    distill_path('faq', views.faq, name="faq", distill_func=get_faq),
    distill_path('legal', views.legal, name="legal", distill_func=get_legal)
]
meeb commented 4 years ago

What's in your views.index function? Also can you paste the full stack trace?

Aside from your error, creating a static version of the login form (or any form) is a bit pointless, it'll render the form but it won't do anything obviously.

agieocean commented 4 years ago

For the views.index function:

def index(request):
    if ageCheck(request) == False:
        return redirect("/ageVerification")
    if isAllowed(request) != True:
        return redirect("/register")
    if request.user.is_authenticated:
        subscriptions = [s.model for s in Subscription.objects.filter(user=request.user, subscribed=True)]
        posts = json.loads(serializers.serialize("json", Post.objects.filter(model__in=subscriptions, available__lt=datetime.datetime.now()).order_by("-available")))
        context = {"groups": [group.name for group in request.user.groups.all()], "posts": posts}
        return render(request, "user/index.html", context)
    else:
        context = {}
        return render(request, "user/index.html", context)

Looking at this do I need to create and pass a request object, I think I naively assumed that would be handled automatically but let me know when you have a chance ^_^ Here's the full output:


python manage.py distill-local site-export

You have requested to create a static version of
this site into the output path directory:       

    Source static path:  .
    Distill output path: [redacted]\site-export

Distill output directory exists, clean up?
This will delete and recreate all files in the output dir

Type 'yes' to continue, or 'no' to cancel: yes
Recreating output directory...

Generating static site into directory: [redacted]\site-export
Loading site URLs
CommandError: Failed to render view "/": 'WSGIRequest' object has no attribute 'session'
meeb commented 4 years ago

You do not need to pass in a custom requests object, this is handled for you. However, this view, I suspect, when run via distill won't do anything. It'll return a redirect, and probably throw an error for not returning a 200 status code. You can't distill dynamic actions (decision based redirects, forms, etc.) only static content. For example this will work fine:

def index(request):
    return render(request, "index.html")

But this will not:

def index(request):
    if not request.user.is_authenticated:
        return redirect("/signup")
    return render(request, "index.html")

During building the static site it will always trigger the redirect and result in trying to render a redirect and throwing an error. To make a flat, static site you can only render static views which return HTML and 200 HTTP status code, and it is advisable the views are consistent so always return the same thing when they are called. Obviously a static site can't support any database or session features at all as no code executes anywhere when looking at a static HTML page.

As for your session error, looks like it could be related to the order of your middleware, does this site work normally without distill under ./manage.py runserver? I'm guessing your ageCheck function tries to set some sort of session variable?

agieocean commented 4 years ago

Fantastic thank you so much for the details! This site does normally run fine with ./manage.py runserver and ageCheck is just this:

def ageCheck(request):
    try:
        if request.session['verify'] != "true":
            return False
    except KeyError:
        return False
meeb commented 4 years ago

After a quick search, your error is caused because internally distill uses Django's RequestFactory() to create a request to render the views and generate the HTML to save to disk. The request object returned by RequestFactory() doesn't support sessions by design:

https://docs.djangoproject.com/en/3.1/topics/testing/advanced/#django.test.RequestFactory https://code.djangoproject.com/ticket/15736

This hasn't come up previously as I don't think anyone has attempted to set sessions in a static site before. As said above, the sessions will do nothing in a static site and make no logical sense, however I can see how it may occur that someone uses sessions in an existing site which then gets wrapped in with distill and the error is not obvious.

I've pushed a new release of distill, version 2.1, this includes a dummy session interface which will raise a polite error rather than a hard exception. This should let you know you're doing something weird, but not stop the static site from being generated, so you'll see something like this instead now:

RuntimeWarning: request.session.__setitem__(('test', 'test'), {}) called. This is a dummy interface. The request.session feature of Django is not supported by distill. This request will do nothing and has no effect when rendering a static site. If this request is a requirement for the function of your site when not being rendered you can ignore this warning.

Upgrade to version 2.1 and see if that works better for you.

Obviously, your site has other issues (user interactivity, session based data storage, user messaging etc. based on your URL paths) so you may want to reconsider if making a static site from your project is appropriate in the first place, unless you're making some static archive of an old site. If you do proceed with attempting to make a static version of your dynamic site note your message/<str:username> URL will require its distill_func to return an iterable of username strings to work as the URL has required parameters.

meeb commented 4 years ago

As you've not replied for a week I'll assume this has resolved your issue and close it. Feel free to open another issue if you encounter any more problems.