import pathlib from django import http from django.conf import settings from django.urls import Resolver404, resolve from django.utils.deprecation import MiddlewareMixin from troggle import settings """Non-standard django middleware is loaded from this file. """ todo = """SmartAppendSlashMiddleware(object) Not Working. It needs re-writing. Can we make this work even though we have a catchall url rule ? """ class TroggleAppendSlashMiddleware(MiddlewareMixin): """ "SmartAppendSlash" middleware for taking care of URL rewriting. This middleware appends a missing slash, if: * the SMART_APPEND_SLASH setting is True * the URL without the slash does not exist in urls.py * the URL with an appended slash does exist in urls.py Otherwise it won't touch the URL. MODIFICATION Since we have a universal catchall url pattern in urls.py, the usual way this works won't ever trigger adding a slash. So we check for the existence of a file in expoweb, not the existence of a pattern in urls.py... but site_media.. but css etc.... CONCLUSION This technique "works" but would be a maintence nightmare, so DO NOT USE IT do NOT include troggle.core.middleware.TroggleAppendSlashMiddleware in settings.py FURTHER WARNING If playing about with this, the 301 redirects that it creates will be cached INDEFINITELY by any browser you used to test it, e.g. /css/main.css with be permanetly redirected to /css/main2.css/ with dreadful consequences, similarly for any images visited. You have to go into your browser settings and delete all cached files to recover from this. """ def process_request(self, request): """Called for every url so return as quickly as possible Append a slash if TROGGLE_APPEND_SLASH is set, the resulting URL resolves and it doesn't without the / """ if not settings.TROGGLE_APPEND_SLASH: return None if request.path.endswith("/"): return None if request.path.endswith("_edit"): return None if request.path.startswith("/"): relative_path = request.path[1:] else: relative_path = request.path for root in [settings.MEDIA_ROOT, settings.JSLIB_ROOT, settings.EXPOFILES, settings.SCANS_ROOT, settings.PHOTOS_ROOT]: full_path = root / relative_path print(f"+++++ MIDDLEWARE checking {root} / {relative_path} ") if full_path.is_file(): print(f"+++++ MIDDLEWARE It IS a {root} file {full_path=} so use it as-is.") return None else: print(f"+++++ MIDDLEWARE NOT a {root}file {full_path=}") host = http.HttpRequest.get_host(request) old_url = [host, request.path] # if _resolves(old_url[1]): # return None # So: it does not resolve according to our criteria, i.e. _edit doesn't count, and URL resolves doesn't count because of the catch all new_url = old_url[:] new_url[1] = new_url[1] + "/" if not _resolves(new_url[1]): print(f"+++++ MIDDLEWARE add SLASH and resolves {old_url=} => {new_url=}") return None else: if settings.DEBUG and request.method == "POST": # replace this exception with a redirect to an error page raise RuntimeError( f"You called this URL via POST, but the URL doesn't end in a slash and you have SMART_APPEND_SLASH set. Django can't redirect to the slash URL while maintaining POST data. Change your form to point to {new_url[0]}{new_url[1]} (note the trailing slash), or set SMART_APPEND_SLASH=False in your Django settings." ) if new_url != old_url: # Redirect if new_url[0]: newurl = f"{request.is_secure() and 'https' or 'http'}://{new_url[0]}{new_url[1]}" else: newurl = new_url[1] if request.GET: newurl += "?" + request.GET.urlencode() return http.HttpResponsePermanentRedirect(newurl) return None def _resolves(url): try: # If the URL does not resolve, the function raises a Resolver404 exception (a subclass of Http404) resolve(url) # this will ALWAYS be resolved by expopages because it will produce pagenotfound if not the thing asked for # so handle this in expopages, not in middleware return True except Resolver404: return False except: print(url) raise