Image response from DetailView in Django - django

I'd like to have a DetailView respond with an image file that is saved to the model/object.
I have seen this related question but what I am doing is a little different.
I have the following model/views/urls - this allows me to display the image in an img tag inside a template when accessing the url localhost/MY-SKU-FIELD. I'd rather not use a template and just respond with the image directly.
# models.py
from django.db import models
class SignModel(models.Model):
sku = models.CharField('SKU', max_length=50)
sign_image = models.ImageField(upload_to='images/sign')
# views.py
from django.views.generic import DetailView
from signs.models import SignModel
class SignDetailView(DetailView):
model = SignModel
template_name = 'signs/sign_detail.html'
slug_url_kwarg = 'sku'
slug_field = 'sku'
# urls.py
from django.urls import path
from signs.views import SignDetailView
urlpatterns = [
path('<slug:sku>', SignDetailView.as_view(), name='sign-detail'),
]
{# 'signs/templates/sign_detail.html #}
<img src="{{object.sign_image.url}}" alt="">
Based on the related question/answer, I figure I need to define my own get() method, then fetch the image, and then respond accordingly, but I'm not quite sure how to fetch the image based on the object fetched in DetailView. I've tried fetching with r = requests.get(self.sign_image.url) but that is clearly not working.
How do I get the image and respond correctly?

I haven't done this before but I was able to quickly check, and you can give the value of the field to a django.http.FileResponse object. As for the view, I think you want to overwrite the render_to_response method.
from django.http import FileResponse
class SignDetailView(DetailView):
model = SignModel
template_name = 'signs/sign_detail.html'
slug_url_kwarg = 'sku'
slug_field = 'sku'
def render_to_response(self, context, **kwargs):
return FileResponse(self.object.sign_image)

Related

Acquire model instance as view parameter instead of model id

In laravel, it's possible to have access to entire models with type-hinting the parameter with a model class. for example:
routes/web.php
Route::get('posts/{post}', function(\App\Post $post) {
/*
$post is now the model with pk in url and
if the pk is wrong it throws an 404 automatically
*/
});
How is that possible in django? (with functions as view)
def posts(request, post_id):
post = get_model_or_404(Post, pk=post_id)
# ...
The second line is completely boilerplate and so much repeated in my code.
You can make a custom path converter [Django-doc] for that. We can for example make model path converter:
# app/converters.py
from django.shortcuts import get_object_or_404
from django.urls.converters import IntConverter
class ModelConverter(IntConverter):
model = None
def to_python(self, value):
return get_object_or_404(self.model, pk=super().to_python(value))
def to_url(self, value):
if instanceof(value, int):
return str(value)
return str(value.pk)
def model_converter(model):
class Converter(ModelConverter):
model = model
return Converter
Then in your urls.py you can make model converters and register these once:
# app/urls.py
from app.converters import model_converter
from app import views
from app.models import Post
from django.urls import path, register_converter
register_converter(model_converter(Post), 'post')
urlpatterns = [
path('posts/<post:post>', views.post, name='post'),
# …
]
Then in the view, you obtain the Post object (given a post for that primary key exists) through the post parameter:
app/views.py
def post(request, post):
# post is here a Post object
# …
In your template, you can then just pass the Post object in the {% url … %} template tag, so:
<a href="{% url 'post' mypostobject %}">

How to accept form data and return it along with the results of some processing in Django RESTFramework?

I am trying to understand Django RESTFramework. I am already familiar with Django. I want to create an endpoint that accepts some text data and processes it and returns it to the user along with the results of the processing (in text). I have completed a couple of tutorials on the topic but I still don't understand how it works. Here is an example from a working tutorial project. How can I edit it to achieve my goal? It all looks automagical.
# views.py
from rest_framework import generics
from .models import Snippet
from .serializers import SnippetSerializer
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
​
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
# Here I would like to accept form data and process it before returning it along with the
# results of the processing.
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
Okay, I think you are a newbie in Django rest and try to understand its flow so I can explain it with an example of a subscription plan.
First, create a model in models.py file
from django.db import models
class SubscriptionPlan(models.Model):
plan_name = models.CharField(max_length=255)
monthly_price = models.IntegerField()
yearly_price = models.IntegerField()
Then create views in a view.py file like
from rest_framework.views import APIView
class SubscriptionCreateAPIView(APIView):
serializer_class = SubscriptionSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(
{'message': 'Subscription plan created successfully.',
'data': serializer.data},
status=status.HTTP_201_CREATED
)
and then define a serializer for validation and fields in which we can verify which fields will be included in the request and response object.
serializers.py
from rest_framework import serializers
from .models import SubscriptionPlan
class SubscriptionSerializer(serializers.ModelSerializer):
plan_name = serializers.CharField(max_length=255)
monthly_price = serializers.IntegerField(required=True)
yearly_price = serializers.IntegerField(required=True)
class Meta:
model = SubscriptionPlan
fields = (
'plan_name', 'monthly_price', 'yearly_price',
)
def create(self, validated_data):
return SubscriptionPlan.objects.create(**validated_data)
Now add urls in src/subsciption_module/urls.py
from django.urls import path
from .views import SubscriptionCreateAPIView
app_name = 'subscription_plan'
urlpatterns = [
path('subscription_plan/', SubscriptionCreateAPIView.as_view()),
]
At the end include module url in root urls.py file where your main urls will be located. It will be the same directory which contains settings.py and wsgi.py files.
src/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('src.subscription_plan.urls', namespace='subscription_plan')),
]
That's it. This is how flow works in django rest and you can process data and display data in this way. For more details you can refer django rest docs.
But this is not in any way different from what you do with plain Django. Your SnippetDetail view is just a class-based view, and like any class-based view if you want to do anything specific you override the relevant method. In your case, you probably want to override update() to do your custom logic when receiving a PUT request to update data.

Query database using POST data in FormView class

I'm very new to Django and need help with a simple task. I have two templates.
search.html: renders a simple form with a text field an a submit button and POSTs to /search_results/
search_results.html: need to render the result of database query POSTed to /search_results/
In my forms.py file:
from django import forms
class SystemsForm(forms.Form):
serial_num = form.CharField(label="Serial no.", max_length=45)
In my urls.py
urlpatterns = patterns('',
url(r'^search/$', SystemSearch.as_view()),
url(r'^search_results/$', SystemResult.as_view()),
)
In my views.py file
from django.views.generic.edit import FormView
from inventory.forms import SystemsForm
class SystemSearch(FormView):
# For displaying the form
template_name = 'inventory/search.html'
form_class = SystemsForm
class SystemResult(FormView):
template_name = 'inventory/search_result.html'
form_class = SystemsForm
# How to do these two things?
# Query the database model called Systems with the POST data
# -- Systems.objects.get(serial_num=??)
# pass context to search_result.html
I'm not even sure if I'm heading in the correct direction or completely off from what I should be doing.

ListView and Form on the same view, using the form to filter the results

I'm new to Django and I've been trying to make so small app after reading the tutorial but I can't figure out what's wrong with my code.
What I'm trying to do is listing all the database entries of a model called project using a ListView with a form below it (using the same view) that the user can use to filter the entries to be shown by means of submitting data in text fields.
I managed to make that work, and it looks like this:
However, once the user clicks the "Filter results" button providing some filtering pattern on the textfields (let's say, filter by name = "PROJECT3", leaving the other fields blank), instead of rendering a page with the filtered data and the form below it, which is my intention, it just returns a white page.
Can anyone please explain me what is wrong with my code?
Here are the relevant parts:
forms.py
class FilterForm(forms.Form):
pjt_name = forms.CharField(label='Name', max_length=200, widget=forms.TextInput(attrs={'size':'20'}))
pjt_status = forms.CharField(label='Status', max_length=20, widget=forms.TextInput(attrs={'size':'20'}) )
pjt_priority = forms.CharField(label='Priority', max_length=20, widget=forms.TextInput(attrs={'size':'20'}))
pjt_internal_sponsor = forms.CharField(label='Int Sponsor', max_length=20, widget=forms.TextInput(attrs={'size':'20'}))
pjt_external_sponsor = forms.CharField(label='Ext Sponsor', max_length=20, widget=forms.TextInput(attrs={'size':'20'}))
views.py
from App.models import Project
from django.views.generic import ListView
from django.shortcuts import render
from django.template import RequestContext
from App.forms import FilterForm
class ProjectListView(ListView):
context_object_name = 'project_list'
template_name='App/index.html'
def get_context_data(self, **kwargs):
context = super(ProjectListView, self).get_context_data(**kwargs)
if 'filter_form' not in context:
context['filter_form'] = FilterForm()
return context
def get_queryset(self):
form = FilterForm(self.request.GET)
if form.is_valid():
name = form.cleaned_data['pjt_name']
i_sp = form.cleaned_data['pjt_internal_sponsor']
e_sp = form.cleaned_data['pjt_external_sponsor']
status = form.cleaned_data['pjt_status']
pri = form.cleaned_data['pjt_priority']
return send_filtered_results(name, i_sp, e_sp, status, pri)
else:
return Project.objects.order_by('-project_creation_date')[:5]
def send_filtered_results(name, i_sp, e_sp, status, pri):
return Project.objects.filter(project_name__in=name,internal_sponsor_name__in=i_sp, external_sponsor_name__in=e_sp, project_status__in=status, project_priority__in=pri).exclude(alias__isnull=True).exclude(alias__exact='')
urls.py
from django.conf.urls import patterns, url
from App.views import ProjectListView
from django.views.generic import DetailView
from App.models import Project, Task
urlpatterns = patterns('',
url(r'^$',
ProjectListView.as_view())
The answer is in your response/status code:
After doing that I get a white page and runserver returns [23/Jan/2015 00:21:09] "POST /App/ HTTP/1.1" 405 0
You're POSTing to a view that has no POST handler. You should be getting an error saying so, but the 405 means method not allowed.
Add a post method to your CBV. Django class based views map request method to functions, so a GET is handled via CBV.get, POST via CBV.post
For demonstration purposes, add:
# essentially, mirror get behavior exactly on POST
def post(self, *args, **kwargs):
return self.get(*args, **kwargs)
And change your form handler to pull from request.POST not request.GET.
form = FilterForm(self.request.POST)
I suggest you start using print()s or log to start seeing what's happening. request.GET should be empty, as.. you're not using GET parameters. That data will be in request.POST.
Your code is messy and your Project.objects.filter(...) is far to aggressive. It just doesn't return any objects.
Don't use name__in=name but name__contains=name.

Django custom tag no longer works

I made a custom tag to display a list of categories and its url which worked until I made a category detail view which will only display articles based on the category.
Here is the category view:
from blog.models import Category, Entry
from django.shortcuts import render_to_response, get_object_or_404
from django.views.generic import list_detail
#for template tag to display all categories
def all_categories(request):
return render_to_response('category_detail.html', {
'categories': Category.objects.all()
})
def category_detail(request, slug):
"""
object_list
List of posts specific to the given category.
category
Given category.
"""
return list_detail.object_list(
request,
queryset = Entry.objects.filter(categories = Category.objects.filter(slug = slug)),
extra_context = {'category': Category.objects.filter(slug = slug)},
template_name = 'blog/category_detail.html',
)
Categories url:
from django.conf.urls.defaults import *
from django.views.generic.list_detail import object_list, object_detail
from blog.views.category import category_detail
from blog.models import Category, Entry
# for category detail, include all entries that belong to the category
category_info = {
'queryset' : Category.objects.all(),
'template_object_name' : 'category',
'extra_context' : { 'entry_list' : Entry.objects.all }
}
urlpatterns = patterns('',
url(r'^$', 'django.views.generic.list_detail.object_list', {'queryset': Category.objects.all() }, 'blog_category_list'),
url(r'^(?P<slug>[-\w]+)/$', category_detail),
)
and the custom category tag:
from django import template
from blog.models import Category
def do_all_categories(parser, token):
return AllCategoriesNode()
class AllCategoriesNode(template.Node):
def render(self, context):
context['all_categories'] = Category.objects.all()
return ''
register = template.Library()
register.tag('get_all_categories', do_all_categories)
Also here is how i am using the custom tag in base.html:
{% load blog_tags %}
<p>
{% get_all_categories %}
<ul>
{% for cat in all_categories %}
<li>{{ cat.title }}</li>
{% endfor %}
</ul>
</p>
Before I added category_detail in the view, the custom tag would display the url correctly like: /categories/news. However, now all of the links from the custom tag just displays the url or the current page your on. Whats weird is that it displays the category name correctly.
Does anyone know how to get the urls to work again?
EDIT
here is my category model, maybe something is wrong with my get_absolute_url():
import datetime
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
class Category(models.Model):
title = models.CharField(max_length = 100)
slug = models.SlugField(unique = True)
class Meta:
ordering = ['title']
verbose_name_plural = "Categories"
def __unicode__(self):
return self.title
#models.permalink
def get_absolute_url(self):
return ('category_detail', (), {'slug': self.slug })
EDIT: updated get_absolute_url for Category model, however, that did not fix my problem. Also if i wasnt clear earlier, the category url worked even before changing the get_absolute_url
I am going to bet that get_absolute_url is actually returning an empty string. This would make the links reload the current page. Check out your HTML and look for something like this:
Category Title Example
If the URLs are actually blank, there is probably an error in get_absolute_url. When a Django template encounters an error when outputting a template variable it returns an empty string. Try calling get_absolute_url from a Django shell and see if it returns properly:
Category.objects.all()[0].get_absolute_url()
It looks like you renamed your view from blog_category_detail to category_detail, but forgot to update the reference in get_absolute_url.
Update: It doesn't look like the reverse URL lookup for 'category_detail' would succeed. Your urls file does not name the category_detail URL. You should either change the get_absolute_url reference to app_name.views.category_detail (or wherever it is stored) or name the URL by replacing that last line in your urls file with:
url(r'^(?P<slug>[-\w]+)/$', category_detail, name='category_detail'),
To find the source of the problem, you should debug this code from a command line. Something like this should do:
$ python manage.py shell
>>> from blog.mobels import Category
>>> cat = Category.objects.all()[0]
>>> cat.get_absolute_url()