How to test login process? - django

I am developing a web application using Django 1.6 where I require users to log in using my login form. I want to write a test case that tests the procedure of a user logging in.
I did succeed to get a working login page, meaning I was able to log in. The following steps explain my setup. Writing a test case for this procedure did however fail. At this point I'm neither able to spot an error in my test, nor sure whether this approach makes sense before all. Can someone help me?
Update: In the meantime I have noticed, that my form error message was too unspecific. The issue appears to be caused by empty form fields that fail some form validation step. When I add the errors {{ form.errors }} to the template, the response contains the following (formatted a little less nicely):
<ul class="errorlist">
<li>username
<ul class="errorlist">
<li>This field is required.</li>
</ul>
</li>
<li>password
<ul class="errorlist">
<li>This field is required.</li>
</ul>
</li>
</ul>
I'm still not sure how to test this procedure most reasonably. I'm quite new to Django and the philosophy of it's testing framework. May it be, that this issue falls not directly into the scope of the Django testing framework, where models, and views, and forms are (seemingly) intended to be tested rather seperated from each other? Does this test rather fall into the realm of an independent test suite doing black-box/functional testing, e.g., using Selenium?
I started like this:
Started a fresh project, like this:
django-admin.py startproject login_test
added an entry to the login_test/urls.py, like this:
urlpatterns = patterns('',
url(r'^login/$', 'django.contrib.auth.views.login'),
url(r'^admin/', include(admin.site.urls)),
)
made up a template at /home/moooeeeep/login_test/templates/registration/login.html, like this:
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
{% if not user.is_active %}
<form method="post" action="{% url 'django.contrib.auth.views.login' %}">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Login</button>
<input type="hidden" name="next" value="{% url 'django.contrib.auth.views.login' %}" />
</form>
{% else %}
<p>You are logged in as <strong>{{ user.username }}</strong>.</p>
{% endif %}
added the template directory to the TEMPLATE_DIRS in the login_test/settings.py, like this:
TEMPLATE_DIRS = (
'/home/moooeeeep/login_test/templates/',
)
Sync'ed the database to get the auth-related tables, and added a superuser like this:
python manage.py syncdb
If I did not miss a step, at this point I am able to log into my site. When logged in I get displayed my username, if not I see the form, if login fails it displays an error message in addition.
My test case however, after submitting correct post data, gets to see the failed login response, with the user not being logged in.
Here's my login_test/tests.py:
from django.contrib.auth.models import User
from django.contrib.auth import SESSION_KEY
from django.test import TestCase
class LogInTest(TestCase):
def setUp(self):
self.credentials = {
'username': 'testuser',
'password': 'secret'}
User.objects.create_user(**self.credentials)
def test_login(self):
# login
response = self.client.post('/login/', **self.credentials)
# should be logged in now, fails however
self.assertTrue(response.context['user'].is_active)
Here's the output:
$ python manage.py test
Creating test database for alias 'default'...
F
======================================================================
FAIL: test_login (login_test.tests.LogInTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/moooeeeep/login_test/login_test/tests.py", line 16, in test_login
self.assertTrue(response.context['user'].is_active)
AssertionError: False is not true
----------------------------------------------------------------------
Ran 1 test in 0.008s
FAILED (failures=1)
Destroying test database for alias 'default'...
Here's my login_test/settings.py:
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '+6e-6t8dnl$75fx%=7y$+4ipo&i=kvw(p*963p37)7o$1-)30u'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
TEMPLATE_DEBUG = True
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
ROOT_URLCONF = 'login_test.urls'
WSGI_APPLICATION = 'login_test.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
TEMPLATE_DIRS = (
'/home/moooeeeep/login_test/templates/',
)

I found out that my passing of the post parameters was wrong (the dictionary must not be unpacked). I also needed to learn, that I needed to follow the redirect issued by the post request after sending the data. Then my login test worked (although the page that was target of the redirection does not exist yet).
Here's my updated test case:
from django.contrib.auth.models import User
from django.test import TestCase
class LogInTest(TestCase):
def setUp(self):
self.credentials = {
'username': 'testuser',
'password': 'secret'}
User.objects.create_user(**self.credentials)
def test_login(self):
# send login data
response = self.client.post('/login/', self.credentials, follow=True)
# should be logged in now
self.assertTrue(response.context['user'].is_active)
Here's the output:
$ python manage.py test
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.196s
OK
Destroying test database for alias 'default'...

You don't want to be checking "is_active" boolean, you want to be checking
response.context['user'].is_authenticated
see:
https://docs.djangoproject.com/en/dev/topics/auth/default/#authentication-in-web-requests
and:
https://docs.djangoproject.com/en/dev/ref/contrib/auth/

Related

No CSRF token after Django 1.8 form error

I recently upgraded my website to Django 1.8 and am getting an "ImportError No module named lib" when my signup (user registration) form is posted. Specifically, if a user is signing up for an account and their passwords don't match, my form will display an error. If they then enter matching passwords, I get the following Django error:
Request Method: POST
Request URL: http://localhost:8000/create_account/
Django Version: 1.8.4
Python Version: 2.7.5
Installed Applications:
['django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.admin',
'account',
'home',
'members',
'profile']
Installed Middleware:
('django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware')
Traceback:
File "/Users/me/venv/scores/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
125. response = middleware_method(request, callback, callback_args, callback_kwargs)
File "/Users/me/venv/scores/lib/python2.7/site-packages/django/middleware/csrf.py" in process_view
189. return self._reject(request, REASON_BAD_TOKEN)
File "/Users/me/venv/scores/lib/python2.7/site-packages/django/middleware/csrf.py" in _reject
101. return _get_failure_view()(request, reason=reason)
File "/Users/me/venv/scores/lib/python2.7/site-packages/django/middleware/csrf.py" in _get_failure_view
33. return get_callable(settings.CSRF_FAILURE_VIEW)
File "/Users/me/venv/scores/lib/python2.7/site-packages/django/utils/lru_cache.py" in wrapper
101. result = user_function(*args, **kwds)
File "/Users/me/venv/scores/lib/python2.7/site-packages/django/core/urlresolvers.py" in get_callable
112. if submod and not module_has_submodule(import_module(parentmod), submod):
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/importlib/__init__.py" in import_module
37. __import__(name)
Exception Type: ImportError at /create_account/
Exception Value: No module named lib
As you can see from the stacktrace, when the form is posted with matching passwords, the response is being deemed invalid by the middleware before control is even returned to the create_account view that displayed the form. I set a breakpoint in the urlresolvers.py module and found that the module that can't be found is "lib.local_csrf". I should add that the form itself is composed of both a signin (authentication) form and the signup (registration) form in question and both forms contain csrf_token tags:
# home/templates/home.html
<form action="{% url 'sign-in' %}" method="post">
{% csrf_token %}
<!-- error fields appear here -->
{% for field in sign_in_form.visible_fields %}
{{ field.label_tag }}
{{ field }}
<input type="submit" value="Sign in">
Forgot password?
</form>
<form action="{% url 'create-account' %}" method="post">
{% csrf_token %}
<!-- error fields appear here -->
{% for field in create_account_form.visible_fields %}
{{ field.label_tag }}
{{ field }}
{% endfor %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
<input type="submit" value="Sign up">
</form>
Here is the create_account view:
# account/views.py
def create_account(request, template):
# Initialize the signin form on the same page.
sign_in_form = SignInAuthenticationForm(request, label_suffix="")
if request.method == "POST":
create_account_form = CreateAccountForm(request.POST, label_suffix="")
if create_account_form.is_valid():
user = User.objects.create_user(
username = create_account_form.cleaned_data['username'],
password = create_account_form.cleaned_data['password1']
)
# Create a new account.
account = Account(user=user)
account.save()
# Do other stuff and then redirect user to next form
else:
create_account_form = CreateAccountForm(label_suffix="")
return render_to_response(template, locals())
What's most interesting is that when I set a breakpoint in the process_view in django.middleware.csrf, the csrf_token and request_csrf_token variables both contain the same value when the user initially submits the form with mismatched passwords. But when they enter matching passwords and resubmit the form a second time, the csrf_token contains the token value but request_csrf_token is blank. This was confirmed by the fact that request.POST['csrfmiddlewaretoken'] is not in the request.POST dictionary when the user resubmits the form. I don't understand why this is happening.
This form worked perfectly well in Django 1.6 but it's stopped working in 1.8 (or possibly 1.7). I looked through the 1.7 and 1.8 release notes and didn't see any changes to the CSRF functionality. I also read the template docs that pertain to CSRF and didn't see anything new there. Doing an online search on this error didn't turn up anything either.
I suspect that some of you will suggest that I use the django-registration-redux library. I don't think I can use it due to my registration workflow. You see, after the user registers on this form, they're redirected first to a page when they enter their user profile, then to a page where they can upload a photo, and then to a page to review and approve their profile and photo. After that, they're forwarded to my payment processor. They're only truly registered with a valid account if my payment processor tells me their payment method has been approved.
Thanks.
The actual error No module named lib appears to be because Django can't import the CSRF_FAILURE_VIEW you have specified in your settings. You could try removing it from your settings, then Django should show the default CSRF failure page instead of the error. I can't tell why the import is failing, because you haven't shown what CSRF_FAILURE_VIEW or your project structure is.
To fix the CSRF error, try using the render shortcut instead of render_to_response. It renders the template with a request context, so that the csrf_token tag works.
# avoid locals() if possible!
return render(request, template, {...})

Django csrf token missing or incorrect error

I have been developing a site using Django and am trying to implement a simple form, based off of one of the models inside my app. I have a few issues with getting it to work, but my current major problem is that I keep receiving the following error: CSRF token missing or incorrect.
I am running Django 1.6 with python 2.7.
I have already looked over the following posts to try fix my problem (and various other solutions to which I give contextual reference to where appropriate), but it has not worked for me:
Django: CSRF token missing or incorrect - Basically, passing RequestContext along with my render_to_response return.
CSRF Token missing or incorrect - I have made sure that django.middleware.csrf.CsrfViewMiddleware appears in my settings.py file and I have tried to add the django.core.context_processors.csrf as instructed to my TEMPLATE_CONTEXT_PROCESSORS setting but there is no change. When I check these settings in the shell, I get the following output:
> from django.conf import settings
> settings.TEMPLATE_CONTEXT_PROCESSORS
('django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug', 'django.core.context_processors.i18n',
'django.core.context_processors.media', 'django.core.context_processors.static',
'django.core.context_processors.tz',
'django.contrib.messages.context_processors.messages')
I placed the following code in my settings.py file but I continue to get the 403 CSRF token error:
import django.conf.global_settings as DEFAULT_SETTINGS
TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + (
'django.core.context_processors.csrf',
)
I have also followed the suggestions given in the error message itself, i.e. makign sure I have the {% csrf_token %} tags in place, using RequestContext instead of Context.
from my views.py file:
from django.shortcuts import render
from django.views.decorators.csrf import csrf_protect, csrf_exempt
from django.template import loader, Context
from django.http import HttpResponse
from forms import StudentForm
from django.http import HttpResponseRedirect
from django.core.context_processors import csrf
from django.template import RequestContext
def edit(request):
form = StudentForm(request.POST or None)
if form.is_valid():
form.save()
return Redirect('/myprofile/')
return render(request, 'myprofile.html',{'form':form})
Please not that I have read several other guides to fixing this problem that involve including RequestContext in several different ways: return render_to_response('myprofile.html', RequestContext(request, {})) and return render_to_response('myprofile.html', RequestContext(request)), none of which worked for me.
my settings.py file:
import django.conf.global_settings as DEFAULT_SETTINGS
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myprofile',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + (
'django.core.context_processors.csrf',
)
My html code is as follows:
<form action = "/myprofile/" method = "post"> {% csrf_token %}
<ul>
{{ form.as_ul }}
</ul>
<input type = "submit" name = "Submit" value = "Edit Form">
</form>
Please not that I have also attempted to add the token as a hidden input, but this has not solved my issue. The function that generates this view is also the same function that is referred to by the form's action, <form action = "/myprofile/" ...>.
Any assistance with this issue would be greatly appreciated.
Your problem is here:
return render_to_response('myprofile.html',{},RequestContext(request))
Although you have added the csrf:
c = {}
c.update(csrf(request))
You don't do anything with c. To solve this once and for all, just use the render shortcut:
from django.shortcuts import render, redirect
def edit(request):
form = StudentForm(request.POST or None)
if form.is_valid():
form.save()
return redirect('/myprofile/')
return render(request, 'myprofile.html', {'form': form})
Next, in your template, you only use {% csrf_token %} not both it and {{ csrf_token }}. The tag will render the form field.
{{ csrf_token }} seems to be empty and overrides the hidden input field which is generated by {% csrf_token %}. just remove the hidden input field with {{ csrf_token }} from your template and it should work probably.
Also, as you are using RequestContext you don't have to add the CSRF token manually to the template, so you can remove following code from your view.
c = {}
c.update(csrf(request))
See https://docs.djangoproject.com/en/1.6/ref/contrib/csrf/#how-csrf-works for more information.

Url namespace not being registered for python social auth

I have a legacy application using django 1.4.2 and python-social-auth.
I have the app installed
INSTALLED_APPS = (
...
'social.apps.django_app.default',
...
)
The backends:
AUTHENTICATION_BACKENDS = (
'social.backends.facebook.FacebookAppOAuth2',
'social.backends.facebook.FacebookOAuth2',
'social.backends.google.GoogleOAuth',
'social.backends.google.GoogleOAuth2',
'social.backends.google.GoogleOpenId',
'django.contrib.auth.backends.ModelBackend',
)
More settings...
SOCIAL_AUTH_FACEBOOK_ID = ''
SOCIAL_AUTH_FACEBOOK_SECRET = ''
SOCIAL_AUTH_ENABLED_BACKENDS=('facebook', 'google')
SOCIAL_AUTH_DEFAULT_USERNAME= lambda u: slugify(u)
And in my root url :
urlpatterns += patterns('',
url('', include('social.apps.django_app.urls', namespace='social'))
But I still get this error:
Template error:
In template /home/matias/Proyectos/apuntes/copias/templates/login.html, error at line 9
9 : <p>Ingresá con tu cuenta de <a class="login facebook" href=" {% url 'social:begin' 'facebook' %} {% if request.GET.next %}?next={{ request.GET.next }}{% endif %}">Facebook</a> </p>
Exception Type: NoReverseMatch at /login
Exception Value: u"'social" is not a registered namespace
I don't know what's missing. As far as I can tell I have everything right.
The quoting in the error messages worries me. But the urls.py is fine so maybe it's django formatting being funny.
Any pointer?
Version missconfiguration. For django lower than 1.5 you need to add:
{% load url from future %}
Right up the template.
I was confused because that's not listed in the section about url dispatching in the docs https://docs.djangoproject.com/en/1.4/topics/http/urls/#defining-url-namespaces.
I also had no idea the load templatetag had a from argument...

Django CSRF Cookie Not Setting

I have been trying to set up CSRF protection on my POST forms on my django 1.4.3 site and have been getting the 403 CSRF cookie not set error.
I believe I have followed the doc in detail, followed advice on many forums but all to no avail. I have stripped back the code as much as possible and the cookie just does not set. The csrfmiddlewaretoken is present, so that's not the source of the problem. I'm at my wit's end. No doubt the answer is stupidly simple, I just need a fresh set of eyes to point it out to me...
So here's some code:
#urls.py
...
url(r'^csrf/$', csrf_test, name='csrf_test'),
...
#views.py
...
from django.views.decorators.csrf import csrf_protect
#csrf_protect
def csrf_test(request):
c = {}
c.update(csrf(request))
return render_to_response('csrf.html', c)
...
<!-- csrf.html -->
<html>
<body>
<form action="" method="post">
{% csrf_token %}
<input name="Submit" value="Submit" type="submit"/>
</form>
</body>
</html>
#settings.py
...
MIDDLEWARE_CLASSES = (
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
#for db transactions. Check middleware order if faulty
'django.middleware.transaction.TransactionMiddleware',
# Uncomment the next line for simple clickjacking protection:
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
)
...
TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
'django.core.context_processors.csrf',
'django.core.context_processors.request',
)
...
I am running the django development server on 127.0.0.1:8000.
I have cookies enabled on my browsers and have confirmed cookies are being set from other websites.
I have checked the csrfmiddlewaretoken is being passed as a POST variable.
I always get the error, this is not intermittent.
I have no problem with POST forms when I have disabled the middleware/used #csrf_exempt.
As I say, I'm at my wit's end. What on earth have I missed?
Thanks
Nathan
EDIT:
It now works, but I have no idea what has changed. For anyone reading this in the future I created a new project and app, it created the cookie straight away. Thinking it must have been a settings issue I changed the order of my MIDDLEWARE_CLASSES entries by moving 'django.middleware.csrf.CsrfViewMiddleware' down below 'django.contrib.sessions.middleware.SessionMiddleware'. I ran the original app and it worked. However to test whether the settings change was responsible I reversed the change. It still worked. Go figure.
I hope this helps someone in the future FWIW.
Nathan
if you're using render_to_response then you should probably use a RequestContext
This will handle the csrf protection for you -
def csrf_test(request):
render_to_response('csrf.html', context_instance=RequestContext(request))
You don't need to include the csrf_protect decorator, since you've got the csrf middleware.
def csrf_test(request):
.....
return render(request, 'csrf.html', { ..... })

How to upload a file in Django? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
What is the minimal example code needed for a "hello world" app using Django 1.3, that enables the user to upload a file?
Phew, Django documentation really does not have good example about this. I spent over 2 hours to dig up all the pieces to understand how this works. With that knowledge I implemented a project that makes possible to upload files and show them as list. To download source for the project, visit https://github.com/axelpale/minimal-django-file-upload-example or clone it:
> git clone https://github.com/axelpale/minimal-django-file-upload-example.git
Update 2013-01-30: The source at GitHub has also implementation for Django 1.4 in addition to 1.3. Even though there is few changes the following tutorial is also useful for 1.4.
Update 2013-05-10: Implementation for Django 1.5 at GitHub. Minor changes in redirection in urls.py and usage of url template tag in list.html. Thanks to hubert3 for the effort.
Update 2013-12-07: Django 1.6 supported at GitHub. One import changed in myapp/urls.py. Thanks goes to Arthedian.
Update 2015-03-17: Django 1.7 supported at GitHub, thanks to aronysidoro.
Update 2015-09-04: Django 1.8 supported at GitHub, thanks to nerogit.
Update 2016-07-03: Django 1.9 supported at GitHub, thanks to daavve and nerogit
Project tree
A basic Django 1.3 project with single app and media/ directory for uploads.
minimal-django-file-upload-example/
src/
myproject/
database/
sqlite.db
media/
myapp/
templates/
myapp/
list.html
forms.py
models.py
urls.py
views.py
__init__.py
manage.py
settings.py
urls.py
1. Settings: myproject/settings.py
To upload and serve files, you need to specify where Django stores uploaded files and from what URL Django serves them. MEDIA_ROOT and MEDIA_URL are in settings.py by default but they are empty. See the first lines in Django Managing Files for details. Remember also set the database and add myapp to INSTALLED_APPS
...
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
...
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'database.sqlite3'),
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
}
}
...
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
...
INSTALLED_APPS = (
...
'myapp',
)
2. Model: myproject/myapp/models.py
Next you need a model with a FileField. This particular field stores files e.g. to media/documents/2011/12/24/ based on current date and MEDIA_ROOT. See FileField reference.
# -*- coding: utf-8 -*-
from django.db import models
class Document(models.Model):
docfile = models.FileField(upload_to='documents/%Y/%m/%d')
3. Form: myproject/myapp/forms.py
To handle upload nicely, you need a form. This form has only one field but that is enough. See Form FileField reference for details.
# -*- coding: utf-8 -*-
from django import forms
class DocumentForm(forms.Form):
docfile = forms.FileField(
label='Select a file',
help_text='max. 42 megabytes'
)
4. View: myproject/myapp/views.py
A view where all the magic happens. Pay attention how request.FILES are handled. For me, it was really hard to spot the fact that request.FILES['docfile'] can be saved to models.FileField just like that. The model's save() handles the storing of the file to the filesystem automatically.
# -*- coding: utf-8 -*-
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from myproject.myapp.models import Document
from myproject.myapp.forms import DocumentForm
def list(request):
# Handle file upload
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
newdoc = Document(docfile = request.FILES['docfile'])
newdoc.save()
# Redirect to the document list after POST
return HttpResponseRedirect(reverse('myapp.views.list'))
else:
form = DocumentForm() # A empty, unbound form
# Load documents for the list page
documents = Document.objects.all()
# Render list page with the documents and the form
return render_to_response(
'myapp/list.html',
{'documents': documents, 'form': form},
context_instance=RequestContext(request)
)
5. Project URLs: myproject/urls.py
Django does not serve MEDIA_ROOT by default. That would be dangerous in production environment. But in development stage, we could cut short. Pay attention to the last line. That line enables Django to serve files from MEDIA_URL. This works only in developement stage.
See django.conf.urls.static.static reference for details. See also this discussion about serving media files.
# -*- coding: utf-8 -*-
from django.conf.urls import patterns, include, url
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = patterns('',
(r'^', include('myapp.urls')),
) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
6. App URLs: myproject/myapp/urls.py
To make the view accessible, you must specify urls for it. Nothing special here.
# -*- coding: utf-8 -*-
from django.conf.urls import patterns, url
urlpatterns = patterns('myapp.views',
url(r'^list/$', 'list', name='list'),
)
7. Template: myproject/myapp/templates/myapp/list.html
The last part: template for the list and the upload form below it. The form must have enctype-attribute set to "multipart/form-data" and method set to "post" to make upload to Django possible. See File Uploads documentation for details.
The FileField has many attributes that can be used in templates. E.g. {{ document.docfile.url }} and {{ document.docfile.name }} as in the template. See more about these in Using files in models article and The File object documentation.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Minimal Django File Upload Example</title>
</head>
<body>
<!-- List of uploaded documents -->
{% if documents %}
<ul>
{% for document in documents %}
<li>{{ document.docfile.name }}</li>
{% endfor %}
</ul>
{% else %}
<p>No documents.</p>
{% endif %}
<!-- Upload form. Note enctype attribute! -->
<form action="{% url 'list' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>{{ form.non_field_errors }}</p>
<p>{{ form.docfile.label_tag }} {{ form.docfile.help_text }}</p>
<p>
{{ form.docfile.errors }}
{{ form.docfile }}
</p>
<p><input type="submit" value="Upload" /></p>
</form>
</body>
</html>
8. Initialize
Just run syncdb and runserver.
> cd myproject
> python manage.py syncdb
> python manage.py runserver
Results
Finally, everything is ready. On default Django developement environment the list of uploaded documents can be seen at localhost:8000/list/. Today the files are uploaded to /path/to/myproject/media/documents/2011/12/17/ and can be opened from the list.
I hope this answer will help someone as much as it would have helped me.
Demo
See the github repo, works with Django 3
A minimal Django file upload example
1. Create a django project
Run startproject::
$ django-admin.py startproject sample
now a folder(sample) is created.
2. create an app
Create an app::
$ cd sample
$ python manage.py startapp uploader
Now a folder(uploader) with these files are created::
uploader/
__init__.py
admin.py
app.py
models.py
tests.py
views.py
migrations/
__init__.py
3. Update settings.py
On sample/settings.py add 'uploader' to INSTALLED_APPS and add MEDIA_ROOT and MEDIA_URL, ie::
INSTALLED_APPS = [
'uploader',
...<other apps>...
]
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
4. Update urls.py
in sample/urls.py add::
...<other imports>...
from django.conf import settings
from django.conf.urls.static import static
from uploader import views as uploader_views
urlpatterns = [
...<other url patterns>...
path('', uploader_views.UploadView.as_view(), name='fileupload'),
]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
5. Update models.py
update uploader/models.py::
from django.db import models
class Upload(models.Model):
upload_file = models.FileField()
upload_date = models.DateTimeField(auto_now_add =True)
6. Update views.py
update uploader/views.py::
from django.views.generic.edit import CreateView
from django.urls import reverse_lazy
from .models import Upload
class UploadView(CreateView):
model = Upload
fields = ['upload_file', ]
success_url = reverse_lazy('fileupload')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['documents'] = Upload.objects.all()
return context
7. create templates
Create a folder sample/uploader/templates/uploader
Create a file upload_form.html ie sample/uploader/templates/uploader/upload_form.html::
<div style="padding:40px;margin:40px;border:1px solid #ccc">
<h1>Django File Upload</h1>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form><hr>
<ul>
{% for document in documents %}
<li>
{{ document.upload_file.name }}
<small>({{ document.upload_file.size|filesizeformat }}) - {{document.upload_date}}</small>
</li>
{% endfor %}
</ul>
</div>
8. Syncronize database
Syncronize database and runserver::
$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py runserver
visit http://localhost:8000/
Generally speaking when you are trying to 'just get a working example' it is best to 'just start writing code'. There is no code here to help you with, so it makes answering the question a lot more work for us.
If you want to grab a file, you need something like this in an html file somewhere:
<form method="post" enctype="multipart/form-data">
<input type="file" name="myfile" />
<input type="submit" name="submit" value="Upload" />
</form>
That will give you the browse button, an upload button to start the action (submit the form) and note the enctype so Django knows to give you request.FILES
In a view somewhere you can access the file with
def myview(request):
request.FILES['myfile'] # this is my file
There is a huge amount of information in the file upload docs
I recommend you read the page thoroughly and just start writing code - then come back with examples and stack traces when it doesn't work.
I must say I find the documentation at django confusing.
Also for the simplest example why are forms being mentioned?
The example I got to work in the views.py is :-
for key, file in request.FILES.items():
path = file.name
dest = open(path, 'w')
if file.multiple_chunks:
for c in file.chunks():
dest.write(c)
else:
dest.write(file.read())
dest.close()
The html file looks like the code below, though this example only uploads one file and the code to save the files handles many :-
<form action="/upload_file/" method="post" enctype="multipart/form-data">{% csrf_token %}
<label for="file">Filename:</label>
<input type="file" name="file" id="file" />
<br />
<input type="submit" name="submit" value="Submit" />
</form>
These examples are not my code, they have been optained from two other examples I found.
I am a relative beginner to django so it is very likely I am missing some key point.
I also had the similar requirement. Most of the examples on net are asking to create models and create forms which I did not wanna use. Here is my final code.
if request.method == 'POST':
file1 = request.FILES['file']
contentOfFile = file1.read()
if file1:
return render(request, 'blogapp/Statistics.html', {'file': file1, 'contentOfFile': contentOfFile})
And in HTML to upload I wrote:
{% block content %}
<h1>File content</h1>
<form action="{% url 'blogapp:uploadComplete'%}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<input id="uploadbutton" type="file" value="Browse" name="file" accept="text/csv" />
<input type="submit" value="Upload" />
</form>
{% endblock %}
Following is the HTML which displays content of file:
{% block content %}
<h3>File uploaded successfully</h3>
{{file.name}}
</br>content = {{contentOfFile}}
{% endblock %}
Extending on Henry's example:
import tempfile
import shutil
FILE_UPLOAD_DIR = '/home/imran/uploads'
def handle_uploaded_file(source):
fd, filepath = tempfile.mkstemp(prefix=source.name, dir=FILE_UPLOAD_DIR)
with open(filepath, 'wb') as dest:
shutil.copyfileobj(source, dest)
return filepath
You can call this handle_uploaded_file function from your view with the uploaded file object. This will save the file with a unique name (prefixed with filename of the original uploaded file) in filesystem and return the full path of saved file. You can save the path in database, and do something with the file later.
Here it may helps you:
create a file field in your models.py
For uploading the file(in your admin.py):
def save_model(self, request, obj, form, change):
url = "http://img.youtube.com/vi/%s/hqdefault.jpg" %(obj.video)
url = str(url)
if url:
temp_img = NamedTemporaryFile(delete=True)
temp_img.write(urllib2.urlopen(url).read())
temp_img.flush()
filename_img = urlparse(url).path.split('/')[-1]
obj.image.save(filename_img,File(temp_img)
and use that field in your template also.
You can refer to server examples in Fine Uploader, which has django version.
https://github.com/FineUploader/server-examples/tree/master/python/django-fine-uploader
It's very elegant and most important of all, it provides featured js lib. Template is not included in server-examples, but you can find demo on its website.
Fine Uploader: http://fineuploader.com/demos.html
django-fine-uploader
views.py
UploadView dispatches post and delete request to respective handlers.
class UploadView(View):
#csrf_exempt
def dispatch(self, *args, **kwargs):
return super(UploadView, self).dispatch(*args, **kwargs)
def post(self, request, *args, **kwargs):
"""A POST request. Validate the form and then handle the upload
based ont the POSTed data. Does not handle extra parameters yet.
"""
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
handle_upload(request.FILES['qqfile'], form.cleaned_data)
return make_response(content=json.dumps({ 'success': True }))
else:
return make_response(status=400,
content=json.dumps({
'success': False,
'error': '%s' % repr(form.errors)
}))
def delete(self, request, *args, **kwargs):
"""A DELETE request. If found, deletes a file with the corresponding
UUID from the server's filesystem.
"""
qquuid = kwargs.get('qquuid', '')
if qquuid:
try:
handle_deleted_file(qquuid)
return make_response(content=json.dumps({ 'success': True }))
except Exception, e:
return make_response(status=400,
content=json.dumps({
'success': False,
'error': '%s' % repr(e)
}))
return make_response(status=404,
content=json.dumps({
'success': False,
'error': 'File not present'
}))
forms.py
class UploadFileForm(forms.Form):
""" This form represents a basic request from Fine Uploader.
The required fields will **always** be sent, the other fields are optional
based on your setup.
Edit this if you want to add custom parameters in the body of the POST
request.
"""
qqfile = forms.FileField()
qquuid = forms.CharField()
qqfilename = forms.CharField()
qqpartindex = forms.IntegerField(required=False)
qqchunksize = forms.IntegerField(required=False)
qqpartbyteoffset = forms.IntegerField(required=False)
qqtotalfilesize = forms.IntegerField(required=False)
qqtotalparts = forms.IntegerField(required=False)
Not sure if there any disadvantages to this approach but even more minimal, in views.py:
entry = form.save()
# save uploaded file
if request.FILES['myfile']:
entry.myfile.save(request.FILES['myfile']._name, request.FILES['myfile'], True)
I faced the similar problem, and solved by django admin site.
# models
class Document(models.Model):
docfile = models.FileField(upload_to='documents/Temp/%Y/%m/%d')
def doc_name(self):
return self.docfile.name.split('/')[-1] # only the name, not full path
# admin
from myapp.models import Document
class DocumentAdmin(admin.ModelAdmin):
list_display = ('doc_name',)
admin.site.register(Document, DocumentAdmin)