I have been fumbling around with trying to protect Django's media files with no luck so far! I am simply trying to make it where ONLY admin users can access the media folder. Here is my Nginx file.
server {
listen 80;
server_name xxxxxxxxxx;
location = /favicon.ico {access_log off; log_not_found off;}
location /static/ {
alias /home/{site-name}/static_cdn/;
}
location /media/ {
internal;
root /home/{site-name}/;
}
location / {
this is setup and working. Didn't include Code though
}
My Url File
urlpatterns = [
url(r'^media/', views.protectedMedia, name="protect_media"),
]
And my view
def protectedMedia(request):
if request.user.is_staff:
response = HttpResponse()
response['Content-Type'] = ''
response['X-Accel-Redirect'] = request.path
return response
else:
return HttpResponse(status=400)
This is producing a 404 Not Found Nginx error. Does anything look blatantly wrong here? Thanks!
BTW, I have tried adding /media/ to the end of the root URL in the Nginx settings.
This is what fixed this issue thanks to #Paulo Almeida.
In the nginx file I changed what I previosly had too...
location /protectedMedia/ {
internal;
root /home/{site-name}/;
}
My url is...
url(r'^media/', views.protectedMedia, name="protect_media"),
And the View is...
def protectedMedia(request):
if request.user.is_staff:
response = HttpResponse(status=200)
response['Content-Type'] = ''
response['X-Accel-Redirect'] = '/protectedMedia/' + request.path
return response
else:
return HttpResponse(status=400)
This works perfectly! Now only admin users can access the media files stored in my media folder.
This helped me a lot, just a small update and modification:
urls.py:
re_path(r'^media/(?P<path>.*)', protectedMedia, name="protect_media")
views.py:
from urllib.parse import quote
from django.http import HttpResponse
from django.contrib.admin.views.decorators import staff_member_required
#staff_member_required
def protectedMedia(request, path):
response = HttpResponse(status=200)
response["Content-Type"] = ''
response['X-Accel-Redirect'] = '/protectedMedia/' + quote(path)
return response
I had to change the nginx config to the following:
location /protectedMedia/ {
internal;
alias /home/{site-name}/;
}
Notes:
I prefer using the decorator, as it automatically redirects to the login page (when specified in settings) and sets the "next" page.
url() gets deprecated in Django 3.1 so just use re_path() instead
alias instead of root in nginx config: I don't want to have "/protectedMedia/" appear in the url (and it didn't work), see also nginx docs
edit from 2021-06-14: German-Umlauts (äüö etc.) didn't work in media paths so I edited the above answer to include quote from urllib.parse
If you're still stuck somewhere, this gave me further backround information: https://wellfire.co/learn/nginx-django-x-accel-redirects/
Related
I have setup my python project using nginx and gunicorn as described in this tutorial https://djangocentral.com/deploy-django-with-nginx-gunicorn-postgresql-and-lets-encrypt-ssl-on-ubuntu/
so far everything works perfectly fine.
My project is in /root/project_folder/
I want to upload files via the admin page, but I get a bad request error 400. The media folder I want to add the files to is /var/www/my_domain/media (owned by the group www-data). This is also properly configured since I can see the images when I move them manually into that folder.
Do you guys maybe have any idea why the issue may be ?
the main problem is when uploading I need to save the image to the media root:
/var/www/domain/media/images/dad/ProfilePicture/offline_screen.png
But when I send request to view the image the web server returns the following:
/media/var/www/domain/media/images/dad/ProfilePicture/offline_screen.png
The upload_to path needs to be images/dad/ProfilePicture/offline_screen.png for correct request but then the image is uploaded to the wrong folder
Any ideas ? Thanks in advance!
UPDATE:
Here is my nginx config
`
server {
server_name domain.net www.domain.net ip_address;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /var/www/domain;
}
location = /media/ {
root /var/www/domain;
}
location / {
include proxy_params;
proxy_pass http://unix:/var/log/gunicorn/domain.sock;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/domain/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/domain/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = www.domain) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = domain) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name domain www.domain ip_address;
return 404; # managed by Certbot
}
`
Here is my media root / media url:
MEDIA_ROOT = '/var/www/domain/media' #os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
The url.py:
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('webpages.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL,document_root=settings.STATIC_ROOT)
And finally how I serve and upload Images:
def get_path_of_content_image(instance, filename):
return os.path.join(settings.MEDIA_ROOT, "images", str(instance.course.topic), str(instance.course), filename)
class Topic(models.Model):
title = models.CharField(max_length=100)
description = models.TextField(max_length=445)
image = models.ImageField(upload_to=get_topic_profile_path, default="images/default.jpeg")
def __str__(self):
return self.title
def image_tag(self):
if self.image:
return mark_safe('<img src="%s" style="width: 200px; height:200px;" />' % self.image.get_image())
def get_image(self):
if self.image:
return str(self.image.url).replace('/media/var/www/domain', '') ########1
else:
return settings.STATIC_ROOT + 'webpages/img/default.jpeg'
image_tag.short_description = 'Current Image'
I wrote this line #######1 since set the upload path to the full media root but then in order to return the correct path I need to remove the media root prefix. And also very importantly this only works if DEBUg set to TRUE.
I'm not certain this will solve your issue, but there are 2 things that need to be configured correctly here. If nginx is serving the files in your media directory ok, I'm guessing your nginx config is already ok and you just need to edit settings.py, but I'll include the nginx config in case it helps someone else.
UPDATE
I don't think those methods in your models are necessary, although I don't really know how they are used. Here's an example of how I configure a model with an ImageField:
Firstly, how to upload and save images
(letting django do most of the work)
models.py
...
class Post(models.Model):
"""Represents a single blog post"""
# model fields
title = models.CharField(max_length=150, unique=True)
content = models.TextField()
title_image = models.ImageField(upload_to='image/', blank=True, null=True)
...
forms.py
...
class PostModelForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'title_image', 'content']
widgets = {
'content': Textarea(attrs={'cols': 80, 'rows': 25}),
}
...
views.py
...
def new_post(request):
context = {}
template = 'new_post.html'
# POST
if request.method == 'POST':
form = PostModelForm(request.POST or None, request.FILES)
if form.is_valid():
new_post = form.save()
return redirect('list_post')
else:
context['form'] = form
return render(request, template, context)
# GET
else:
context['form'] = PostModelForm()
return render(request, template, context)
...
new_post.html (note the enctype of the form - important if you want to upload files)
...
<div class="card bg-light">
<div class="card-header">
<h3>New Post</h3>
</div>
<div class="card-body">
<form action="{% url 'blog:new_post' %}" method="POST" class="w-100" enctype="multipart/form-data">
<div class="col-12">
{% csrf_token %}
{{ form.as_p }}
</div>
<div class="col-12">
<ul class="actions">
<button class="primary" type="submit">Save</button>
</ul>
</div>
</form>
</div>
</div>
...
Secondly, how to serve your images
Now you want to display the image in you markup
In views.py
...
def detail(request, id):
context = {}
template_name = 'post.html'
post = get_object_or_404(Post, id=id)
context['post'] = post
return render(request, template_name, context)
...
In your markup in post.html
...
{% if post.title_image %}
<span class="img"><img src="{{ post.title_image.url }}" alt="image for {{ post.title }}" /></span>
{% endif %}
...
(1) Define MEDIA_ROOT and MEDIA_URL in settings.py
In settings.py add
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
Note: Read the django docs on MEDIA_ROOT and MEDIA_URL
(2) define media location in nginx server block
In that tutorial the nginx configuration doesn't define a location for media. Here's the server block from the tutorial:
server {
listen 80;
server_name server_domain_or_IP;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /path/to/staticfiles;
}
location / {
include proxy_params;
proxy_pass http://unix:/var/log/gunicorn/project_name.sock;
}
}
I think it should look something like this:
server {
listen 80;
server_name server_domain_or_IP;
location = /favicon.ico { access_log off; log_not_found off; }
location /media/ {
root /path/to/media;
}
location /static/ {
root /path/to/staticfiles;
}
location / {
include proxy_params;
proxy_pass http://unix:/var/log/gunicorn/project_name.sock;
}
}
If that configuration doesn't work, I've posted what works for me below
In my own experience with django, nginx and gunicorn however, my nginx configuration looks like this:
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/letsencrypt/live/website_name.com-0001/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/website_name.com-0001/privkey.pem; # managed by Certbot
server_name website_name.com www.website_name.com;
location = /favicon.ico { access_log off; log_not_found off; }
location /media {
alias /home/user_name/website_name/project_name/media;
}
location /static/ {
alias /home/user_name/website_name/project_name/static_root/;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}
Note 1: Here are the nginx docs for alias and root directives.
So after spending an eternity on debugging, I have the solution to my problem:
First Thing that was problematic is the '=' between 'location' and '/media/' in the nginx file. Be sure that this is not root of the error. It should look like this:
location /media {
alias /home/user_name/website_name/project_name/media;
}
and not like this:
location = /media {
alias /home/user_name/website_name/project_name/media;
}
The file upload to a custom local directory can be done like this:
from django.core.files.storage import FileSystemStorage
fs = FileSystemStorage(location=settings.MEDIA_ROOT)
and for the model add the file storage option on upload (or change the name of the image with the updated path afterwards):
image = models.ImageField(upload_to=get_topic_profile_path, default="images/default.jpeg", storage=fs)
thanks for the other answers. I learned quite a lot by fixing this erros :D
Im trying to use curl to hit an endpoint in my Django application and havent been successful returning my data.
curl 127.0.0.1:8000/myapp/?email=myname#gmail.com&part=123434
my server shows a 301 when the curl goes through, however; none of the print statements are ran in my view, and i am not able to get the querystring parameters using request.GET.get().
[21/Aug/2018 00:26:59] "GET /myapp/?email=myname#gmail.com HTTP/1.1" 301 0
view.py
def index(request):
if request.method == 'GET':
print('hello world')
email = request.GET.get('email')
part = request.POST.get('part')
print(email)
print(part)
df = generate_dataframe('apps/myapp/data.csv')
df = get_dataframe_by_part(part, df)
bool = check_all(email, df)
response_data = {}
response_data['DoesUserExist'] = bool
return HttpResponse(json.dumps(response_data), content_type="application/json")
urls.py
urlpatterns = patterns('',
url(r'myapp/', include('myapp.urls')),
)
myapp/urls.py
urlpatterns = patterns('',
url(r'^$', 'myapp.views.index', name='index'),
)
The description for 301 error is
The HTTP response status code 301 Moved Permanently is used for
permanent URL redirection, meaning current links or records using the
URL that the response is received for should be updated.
from 301 error
Hence the issue could be different. check first, whether you can able to load from chrome and the urls are correct
I am trying to build a simple blog application on an ubuntu droplet hosted on digital ocean.
I have the DJango app in mid development and I am getting a No reverse match for a view that existes
Here is the error:
'NoReverseMatch at / Reverse for 'post_detail' not found. 'post_detail' is not a valid view function or pattern name.'
Here is my Url Patterns:
from . import views
urlpatterns = [
url(r'^$', views.post_list, name='post_list'),
url(r'^post/(?P<pk>\d+)/$', views.post_detail, name='post_detail'),
]
~
Here is My view
from django.shortcuts import render, get_object_or_404
def post_list(request):
posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
return render(request, 'blog/post_list.html', {'posts': posts})
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk)
return render(request, 'blog/post_detail.html', {'post': post})
~
Here is My nginx file:
server {
listen 80;
server_name XXX.XXX.XXX.XXX;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/XXXX/Django;
}
location / {
include proxy_params;
proxy_pass http://unix:/home/XXX/Django/BizBlog.sock;
}
}
Where is the error occurring? I can't figure this out, I have worked with Django before and know it has to do with the url searching for the string patterns in my view function and being unable to find it but the function is there. What is wrong?
Here is my main url.py:
from django.contrib import admin
from django.urls import path
from django.conf.urls import include
from django.conf.urls import url
urlpatterns = [
path('admin/', admin.site.urls),
url(r'', include('blog.urls')),
]
Just stop the django development server process, restart the services waitress on windows/gunicorn on linux and nginx on windows/linux.
I've added this to my .htacces:
RewriteEngine on
RewriteCond %{HTTP_HOST} ^www\.
RewriteRule ^(.*)$ http://example.com/$1 [R=301,L]
but then trying to reach www.example.com redirects me to:
http://example.com/example/wsgi.py/
because i have WSGIScriptAlias / home/www/example.com/example/wsgi.py directive in my httpd.conf and of course i get 404 error.
Eventually, i've managed to fix this by adding next line in my urls.py:
url(r'^example/wsgi.py/$', index), (so it redirects to home page)
but i'm not quite sure that this is the right way to do it (because when i try to reach example.com i see that web browser changes address quickly to www.example.com and then again to example.com)
If anyone would ask, yes i've seen this but this didn't help me either, because browser gets url recursive problem (example.com/example.com/example.com/example.com/example.com...)
EDIT : FOLDER STRUCTURE
This is my folder structure:
\mysite\
static\
media\
.htaccess
manage.py
mysite\
templates
templatetags
tinymce
static
urls.py
settigs.py
views.py
wsgi.py
models.py
forms.py
__init__.py
I know this has been answered some time back but to add to the answer given above use
host.startswith('www.')
its more readable and also you should use permanent redirect to give the browser correct response header.
from django import http
class NoWWWRedirectMiddleware(object):
def process_request(self, request):
host = request.get_host()
if host.startswith('www.'):
if request.method == 'GET': # if wanna be a prefect REST citizen, consider HEAD and OPTIONS here as well
no_www_host = host[4:]
url = request.build_absolute_uri().replace(host, no_www_host, 1)
return http.HttpResponsePermanentRedirect(url)
I find it much simpler to accomplish no-www redirects with middleware that with with Apache mod_rewrite config.
The middleware that you linked to looks like it does the trick. I'm guessing your problems came from Apache config - make sure you remove all mod_rewrite commands (Rewrite* stuff) and then restart the apache server (ref. Apache docs but check for your OS, might be specific).
There is only one additional tweak that you should to: make sure you don't redirect any POST requests, because that might result in lost data (tangent ref: Why doesn't HTTP have POST redirect?).
Anyways, this is what I use, worked quite well for me:
from django.http import HttpResponseRedirect
class NoWWWRedirectMiddleware(object):
def process_request(self, request):
if request.method == 'GET': # if wanna be a prefect REST citizen, consider HEAD and OPTIONS here as well
host = request.get_host()
if host.lower().find('www.') == 0:
no_www_host = host[4:]
url = request.build_absolute_uri().replace(host, no_www_host, 1)
return HttpResponseRedirect(url)
To use it, put in a file somewhere, maybe mysite/mysite/middleware.py.
Then make sure it's run, in your settings.py:
MIDDLEWARE_CLASSES = (
'mysite.middleware.NoWWWRedirectMiddleware',
# ... other middleware ...
If there is no MIDDLEWARE_CLASSES in your settings.py then copy the defaults from here in the Django docs but make you're looking at the correct version of Django, there are some changes in 1.7!
Modified version that works with Django 1.11+ style middleware:
from django.http import HttpResponsePermanentRedirect
class NoWWWRedirectMiddleware:
def __init__(self, get_response=None):
self.get_response = get_response
def __call__(self, request):
response = self.process_request(request)
return response or self.get_response(request)
def process_request(self, request):
host = request.get_host()
if host.startswith('www.'):
if request.method == 'GET':
no_www = host[4:]
url = request.build_absolute_uri().replace(host, no_www, 1)
return HttpResponsePermanentRedirect(url)
This Apache configuration works for me:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www.example.com$ [NC]
RewriteRule ^(.*)$ http://example.com$1 [R=301,L]
WSGIScriptAlias / /home/www/example.com/example/wsgi.py
WSGIPythonPath /home/www/example.com/example
<Directory /home/www/example.com/example>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
If a user type a random url(http://testurl/cdsdfsd) for a site ,how to issue page not found.I there any changes settings.py or how to handle this..
The django tutorial and docs have sections you should read.
You need to override the default 404 view.
in your urlconf:
handler404 = 'mysite.views.my_custom_404_view'
By default, django is rendering a 404.html template. Crete this file anywhere where your templates are found. E.g. you can create templates directory in your django project root, then add it to the TEMPLATE_DIRS in settings.py:
import os
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
TEMPLATE_DIRS = (
os.path.join(BASE_DIR, 'templates/'),
)
another solution is to write your own middleware which will check if there was 404 response, and the to decide what to display. This is especially useful if you want to have a fallback solution (like static pages) or to play with the response and e.g. perform a search on the site and show possible options.
here is an example middleware from django.contrib.flatpages. it check if url is defined in the database, if so - returns this page if not, pass and return default response.
class FlatpageFallbackMiddleware(object):
def process_response(self, request, response):
if response.status_code != 404:
return response # No need to check for a flatpage for non-404 responses.
try:
return flatpage(request, request.path_info)
# Return the original response if any errors happened. Because this
# is a middleware, we can't assume the errors will be caught elsewhere.
except Http404:
return response
except:
if settings.DEBUG:
raise
return response