Django often returns outdated view - python

I'm pretty new to web development (not to programming), but I just successfully (sort of) deployed a really basic hello-world style Django app. The first time I did it, I had an issue in my HTML. Here's my whole view with the error:
from django.http import HttpResponse
import datetime
def homepage(request):
now=datetime.datetime.now()
html="<html><body><It is now %s.</body></html>" % now
return HttpResponse(html)
The extra < just after the first body tag caused the browser to display a blank page. I figured out what I did and fixed the error. I also added a title so I'd be able to track what's going on (somewhat) better. The old view became this:
from django.http import HttpResponse
import datetime
def homepage(request):
now=datetime.datetime.now()
html="<html><head><title>Hello</title></head><body>It is now %s.</body></html>" % now
return HttpResponse(html)
Now the browser displays the old view (a blank page) most of the time, just the title with a blank body sometimes, and occasionally the whole correct new view. I have no clue what's going on. I'm running nginx with flup to handle the FastCGI. Ideas?

After changing your code you need to restart your FastCGI server, not Nginx.

Related

Django- redirect to homepage when a invalid path is typed [duplicate]

I thought I had this figured out, but just discovered something weird.
In urls I have
url('^page_1/$', handle_page_1),
url('^page_2/$', handle_page_2),
url('^.*/$', handle_page_not_found),
handle_page_not_found() redirect the user appropriately if the url is not recognized.
That works fine, but I discovered something weird.
If a function returns
return HttpResponse("ok")
then "ok" is returned and everything seems to work fine. BUT, I just saw that handle_page_not_found() is also called (I tested with a print statement). It's still "ok" that is returned, but it's first executing the code in handle_page_not_found().
So, how can I have a function that is called for unrecognized urls, but that is not called by a HttpResponse object?
EDIT: Based on answer, saw that my code is actually fine except in special test situation. It's all good so long as the HttpResponse is returned to an ajax call (which is when I normally use it).
avoid the matter with this, it works with me.
urls.py:
urlpatterns = patterns('',
url('^page_1/$', handle_page_1),
url('^page_2/$', handle_page_2),
)
handler404='views.handle_page_not_found_404'
views.py :
def handle_page_not_found_404(request):
page_title='Page Not Found'
return render_to_response('404.html',locals(),context_instance=RequestContext(request))
For more details see: Django documentation: customizing-error-views
class Redirect404Middleware(object):
def process_response(self, request, response):
if response == Http404:
return HttpResponsePermanentRedirect('/')
return response
The most likely cause is that your browser is doing multiple request to your website.
In particular, it might be trying to request for /favicon.ico/.
You can be sure by displaying request.path when your handler is called.
It works for my app with Django v1.11.3 and python v3.6.
Step 1. add view_404 in views.py as following.
def view_404(request):
# do something
return redirect('/')
Step 2. add handler on urls.py.
url(r'^.*/$', views.view_404)

Assertion error parsing response to POST request from DRF viewset with DRF APIClient in tests

I've run into a strange issue with Django Rest Framework testing engine. The weird thing is that everything used to work fine with Django 3 and this issue turned up after I migrated to Django 4. Apart from testing, everything works well, and responds to queries as expected.
The problem
I'm using DRF APIClient to make queries for unit tests. While GET requests perform predictably, I fail to make POST requests work.
Here is some minimalistic example code I created to figure out the issue. The versions I'm using:
Python 3.9
Django==4.0.3
djangorestframework==3.13.1
from django.db import models
from django.urls import include, path
from django.utils import timezone
from rest_framework import routers, serializers, viewsets
router = routers.DefaultRouter()
# models.py
class SomeThing(models.Model):
created_at = models.DateTimeField(default=timezone.now)
title = models.CharField(max_length=100, null=True, blank=True)
# serializers.py
class SomeThingSerializer(serializers.ModelSerializer):
class Meta:
fields = "__all__"
model = SomeThing
# views.py
class SomeThingViewSet(viewsets.ModelViewSet):
queryset = SomeThing.objects.all().order_by('id')
serializer_class = SomeThingSerializer
# urls.py
router.register("some-things", SomeThingViewSet, basename="some_thing")
app_name = 'question'
urlpatterns = (
path('', include(router.urls)),
)
Here is my test case:
import json
from django.contrib.auth import get_user_model
from rest_framework import status
from rest_framework.test import APITestCase, APIClient
class TestUserView(APITestCase):
self.some_user = get_user_model().objects.create(login="some_user#test.ru")
#staticmethod
def get_client(user):
client = APIClient()
client.force_authenticate(user=user)
return client
def test_do_something(self):
client = self.get_client(self.compliance_chief)
url = reverse('question:some_things-list')
resp = client.post(
path=url,
data=json.dumps({"title": "Created Something"}),
content_type="application/json",
)
assert resp.status_code == status.HTTP_201_OK
(Yes, I have to use some authentication to get access to the data, but I don't think it is relevant to the problem.) To which I receive a lengthy traceback, ending with an assertion error:
File "/****/****/****/venv/lib/python3.9/site-packages/django/test/client.py", line 82, in read
assert (
AssertionError: Cannot read more than the available bytes from the HTTP incoming data.
As it is really fairly long, I'll leave it just in case in a gist without posting it here.
Steps to fix
The problem clearly happens after the correct response is returned by the viewset. To make sure the response is correct I made a slight customisation in the create method to print out the response before it is returned, like so:
class SomeThingViewSet(viewsets.ModelViewSet):
queryset = SomeThing.objects.all().order_by('id')
serializer_class = SomeThingSerializer
def create(self, request, *args, **kwargs):
response = super().create(request, *args, **kwargs)
print("THIS IS THE RESPONSE FROM THE VIEWSET", response)
return response
And, sure enough, the result is correct:
THIS IS THE RESPONSE FROM THE VIEWSET <Response status_code=201, "text/html; charset=utf-8">
Which makes me think something goes wrong at the parsing stage (actually, the traceback implies the same). I tried to tweak the way I build the query, namely:
using format instead of content type like so: resp = client.post(path=url, data={"title": "Created Something"}, format="json")
using the .generic method instead of .post like so: resp = client.generic(method="POST", path=url, data=json.dumps({"title": "Created Something"}), content_type="application/json")
The result is the same.
From googling I found out that this error indeed has occasionally occurred in connection with DRF APIClient and Django, but really long ago (like this discussion, which claims that the issue was fixed in the later versions of Django).
I'm sure the reason for this behaviour is rather obvious (some stupid mistake most likely) and the solution must be very simple, but so far I've failed to find it. I would be very grateful if somebody shared their experience, if there is any, of dealing with such an issue, or their considerations as to where to move from this deadlock.
Alright, the mystery's been resolved and I'm going to share it here in case somebody runs into something similar, although it would take quite a coincidence, so it is unlikely.
Long story short: I messed up the source code of my Django4.0.3. installed in this project.
Now, how it happened. While I was testing some stuff, I ran into an error, which I failed to locate, so I went along the whole chain of events checking if the output was what I expected it to be. Soon enough I found myself checking the output from functions in the libraries installed under my virtual environment. I realise it's a malpractice to directly modify their code, but as I was working in my local environment with an option to reinstall everything at any moment, I decided it was fine to play with them. As it resulted in nothing, I removed all the code I had added (or so I thought).
After a while I realised what caused the initial error (an overlooked condition in my testing setup), fixed it and tried to run the test. That's when the problem in question showed up.
Later I found out that the same very test performs correctly in an identical environment. Then I suspected that I broke something in my local library code. Next I simply compared the code I had dealt with in my local environment with the code from the official source and soon enough I established the offending line. It happened to be in django/test/client.py, in the definition of the RequestFactory.generic method. Something like this:
...
if not r.get("QUERY_STRING"):
# WSGI requires latin-1 encoded strings. See get_path_info().
query_string = parsed[4].encode().decode("iso-8859-1")
r["QUERY_STRING"] = query_string
req = self.request(**r)
return self.request(**r)
...
The offending line (which I added and forgot to remove) was req = self.request(**r). After I deleted it, everything returned back to normal.

Setting up Redis cache with Django

I have a django project on an ubuntu EC2 node, that performs a computationally intensive long running process, that typically takes over 60 seconds. I need to cache the results. Never having worked with a cache before, I am following http://michal.karzynski.pl/blog/2013/07/14/using-redis-as-django-session-store-and-cache-backend/ to use redis for this. In the article the author refers to https://docs.djangoproject.com/en/1.7/topics/cache/ which contains the following:
given a URL, try finding that page in the cache
if the page is in the cache:
return the cached page
else:
generate the page
save the generated page in the cache (for next time)
return the generated page
This is the basic logic which I will need to implement in my django view. However , although I've read through the remainder of the cache docs, I'm still unclear on how to code this logic into my django view.
My current django view contains;
from django.shortcuts import render
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
def index(request):
# long running process started on request. Resulting html page should be cached
return HttpResponse(html)
How do I code the above logic into my view?

Is Django post_save triggered before/after saving instance to database?

I have a website using Django. Each post is an object called Article. I want to retrieve the post's HTML after saving it so I wrote the following post_save hook:
#receiver(models.signals.post_save, sender=Article)
def _send_article_mentions(sender, instance, **kwargs):
import requests
from django.contrib.sites.models import Site
from urlparse import urljoin
from ParallelTransport.settings import ARTICLES_URL
SITE_URL = 'http://'+Site.objects.get_current().domain
article_url = urljoin( SITE_URL, instance.get_absolute_url() )
import time
time.sleep(20)
r = requests.get(article_url)
error_file = open(ARTICLES_URL+'/'+'error.txt','w')
error_file.write('file started1\n')
m = r.status_code
error_file.write(str(m))
error_file.close()
It basically waits for 20s (added as a test) then tries to retrieve the HTML of the post using its URL, and writes the request status code to a file for debugging.
The problem is I always get status = 404 on the first save, it does work on 2nd and subsequent saves. I thought the way Django works would be, in order:
save instance to database using save(). At this point the post would get a URL
send post_save signal
But then I should be able to retrieve the HTML in post_save. Am I understanding post_save incorrectly?
Added notes:
Putting this code in save() method does not work. Nor should it. The post is added to database at the end of the save() method and so should not have any URL until save() ends.
This is on a production site, not on the development server.
I want to use the links in the HTML to send 'pingbacks' or actually webmention. But all my pingbacks are being rejected because the post does not have a URL yet. This is the bare minimum code that does not work.
Although this is a completely wrong approach(*), the problem is probably in database transactions. Current thread saves the article but within this uncommited transaction you are trying to get these data through another thread (through web server). In that case, this behaviour is fully correct. Either you need to commit before retrieving through another thread or get the HTML by another way.
(*) should be done asynchronously on the background (Celery or other more lightweight async queue app) or you can call the view directly if you want to get the HTML (depending on your view, you may have to forge the request; if too complicated, you can create a helper function that cherrypicks minimal code to render the template). If you only need to call a 3rd party API after you save something, you want to do it asynchronously. If you don't do it, the success of your "save() code" will depend on the availability of your connection or the 3rd party service and you will need to deal with transactions on place where you won't to deal with transactions ;)
Have you tried overriding the object's save method, calling super, waiting and then trying to retrieve the HTML?
Also are you using the development server? It may have issues handling the second request while the first one is still going. Maybe try it on a proper server?
I had similar problem caused probably by the same issue (Asked differently, https://plus.google.com/u/0/106729891586898564412/posts/Aoq3X1g4MvX).
I did not solve it in a proper way, but you can try playing with the database cache, or (seen it in another django database problem) close all of the database connections and requery.
Edit 2:
I have created a simple example (using Django 1.5.5) to test whether this works as intended. As far as I can tell, it does. pre_save fires before a database commit and post_save fires after.
Example detailed:
Two example models.
Article is used for triggering signals.
ArticleUrl is used to log responses from Article.get_absolute_url().
# models.py
from django.db import models
from django.core.urlresolvers import reverse
class Article(models.Model):
name = models.CharField(max_length=150)
def get_absolute_url(self):
"""
Either return the actual url or a string containing '404' if reverse
fails (Article is not saved).
"""
try:
return reverse('article:article-detail', kwargs={'pk': self.pk})
except:
return '404'
class ArticleUrl(models.Model):
article_name = models.CharField(max_length=150)
url = models.CharField(max_length=300)
def __unicode__(self):
return self.article_name + ': ' + self.url
Example views.py and urls.py were omitted as they are simple. I can add them if needed.
# views.py, url.py
Creating pre_save and post_save signals for Article:
# signals.py
from django.db.models import signals
from django.dispatch import receiver
from article.models import Article, ArticleUrl
#receiver(signals.pre_save, sender=Article)
def _log_url_pre(sender, instance, **kwargs):
article = instance
article_url = ArticleUrl(
article_name = 'pre ' + article.name,
url = article.get_absolute_url()
)
article_url.save()
#receiver(signals.post_save, sender=Article)
def _log_url_post(sender, instance, **kwargs):
article = instance
article_url = ArticleUrl(
article_name = 'post ' + article.name,
url = article.get_absolute_url()
)
article_url.save()
Importing my signals.py so Django can use it:
# __init__.py
import signals
After defining the above I went ahead and created a new Article in Django shell (python.exe manage.py shell).
>>> from article.models import *
>>> a = Article(name='abcdd')
>>> a.save()
>>> ArticleUrl.objects.all()
[<ArticleUrl: pre abcdd: 404>, <ArticleUrl: post abcdd: /article/article/8>]
The above example does seem to show that pre_save did indeed not return a url, but post_save did. Both appear to be behaving as intended.
You should check whether your code either deviates from the above example or interferes with program execution in some way.
Edit 1:
Having said that (below), according to What Happens When You Save, the post_save signal should run after a database save.
Could there be other parts of your app/site somehow interfering with this?
Original post:
According to the Django documentation, the post_save signal is sent at the end of save(), not after it.
As far as I understand, Django Signals are synchronous (in-process) so they would stall the actual save(). It won't fully complete until the signals are done.
This isn't always applicable, but have you considered a custom signal you could call after save()?

How can I redirect to a different URL in Django?

I have two pages, one which is to display details for a specific item and another to search for items. Let's say that the urls.py is properly configured for both of them and within views.py for my app, I have:
def item(request, id):
return render(request, 'item.html', data)
def search(request):
#use GET to get query parameters
if len(query)==1:
#here, I want to redirect my request to item, passing in the id
return render(request, 'search.html', data)
What do I do to properly redirect the request? I've tried return item(request, id) and that renders the correct page, but it doesn't change the URL in the address bar. How can I make it actually redirect instead of just rendering my other page? The URL pattern for the item page is /item/{id}. I've looked at the answer to similar questions on SO and the documentation, but still couldn't figure it out.
Edit: I just started learning Python less than a week ago and the other answer isn't clear enough to help me out.
Edit 2: Nvm, not sure what I did wrong before, but it worked when I tried it again.
You can use HttpResponseRedirect:
from django.http import HttpResponseRedirect
# ...
return HttpResponseRedirect('/url/url1/')
Where "url" and "url1" equals the path to the redirect.
Just minute suggestion from the above answer for the change in import statement
from django.http import HttpResponseRedirect
return HttpResponseRedirect('/url-name-here/')
you can try this :
from django.shortcuts import redirect
return redirect(f'/customer/{pk}')

Categories