AIM
I am attempting to:
Create a histogram,
Store it temporary memory,
Pass the image to the template.
I am having trouble with Step 3 above. I suspect that I am making a simple and fundamental error in relation to passing the context data to the template.
ERROR
HTML is rendering with a broken image tag.
CODE
Views.py
class SearchResultsView(DetailView):
...
def get(self, request, *args, **kwargs):
self.get_histogram(request)
return super(SearchResultsView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(SearchResultsView, self).get_context_data(**kwargs)
return context
def get_histogram(self, request):
""" Function to create and save histogram of Hashtag.locations """
# create the histogram
plt.show()
img_in_memory = BytesIO()
plt.savefig(img_in_memory, format="png")
image = base64.b64encode(img_in_memory.getvalue())
context = {'image':image}
return context
Results.html
<img src="data:image/png;base64,{{image}}" alt="Location Histogram" />
SOLUTION
In addition to issues with get and get_context_data as outlined by #ruddra below, another issue was that I had to decode the base64 string as a Unicode string. For more information, see here.
To do so, I included: image = image.decode('utf8')
So that, views.py looks like this:
def get_histogram(self, request):
# draw histogram
plt.show()
img_in_memory = BytesIO()
plt.savefig(img_in_memory, format="png") # save the image in memory using BytesIO
img_in_memory.seek(0) # rewind to beginning of file
image = base64.b64encode(img_in_memory.getvalue()) # load the bytes in the context as base64
image = image.decode('utf8')
return {'image':image}
You are calling the get_histogram in wrong way. You can do it like this:
class SearchResultsView(DetailsView):
...
def get_context_data(self, **kwargs):
context = super(SearchResultsView, self).get_context_data(**kwargs)
context.update(self.get_histogram(self.request))
return context
You don't need to call the get_histogram method in get, or override the get method.
Update
I have tried like this:
class SearchResultsView(DetailsView):
...
def get_histogram(self):
x = 2
y = 3
z = 2
t= 3
plt.plot(x, y)
plt.plot(z, t)
plt.show()
img_in_memory = io.BytesIO() # for Python 3
plt.savefig(img_in_memory, format="png")
image = base64.b64encode(img_in_memory.getvalue())
return {'image':image}
def get_context_data(self, *args, **kwargs):
context = super(SearchResultsView, self).get_context_data(*args, **kwargs)
context.update(self.get_histogram())
return context
Output looks like this:
Related
I can't seem to figure out how to add my graph to a Class Detail View? Is it not possible to do so? I add it to the detailView, and call it in my template with:
{{ div | safe }}
But it does not show? I've gotten it to work perfectly fine in a view and template separately.
Here's the whole detailview I'm trying to implement it into.
DetailView
class MedarbejderDetailView(FormMixin, DetailView):
template_name = 'evalsys/medarbejder/detail.html'
model = Medarbejder
form_class = OpretEvalForm
def evalgraph(self):
colors = ["#40e862", "#ff9d26", "#ff1424"]
over = 0
møder = 0
under = 0
none = 0
counts = []
items = ["Overstiger forventning", "Møder forventning", "Under forventning", "Ingen bedømmelse"]
eval_vudering = Evaluering.objects.values("vuderingsnavn__vuderingsnavn")
source = ColumnDataSource(data=dict(items=items, counts=counts))
for i in eval_vudering:
if "Overstiger forventning" in i.values():
over += 1
elif "Møder forventning" in i.values():
møder += 1
elif "Under forventning" in i.values():
under += 1
elif None in i.values():
none += 1
counts.extend([over, møder, under, none])
plot = figure(x_range=items, plot_height=500, plot_width=500, title="Opsumering af evalueringer",
toolbar_location=None, tools="pan, wheel_zoom, box_zoom, reset, tap", tooltips="#items: #counts")
plot.title.text_font_size = "20pt"
plot.vbar(x="items", top="counts", width=0.9, source=source, legend="items", line_color='black',
fill_color=factor_cmap("items", palette=colors, factors=items))
plot.legend.label_text_font_size = "13pt"
script, div = components(plot)
return render(self, 'evalsys/medarbejder/detail.html', {'script': script, 'div': div})
def view_medarbejder_with_pk(self, pk=None):
if pk:
medarbejder = Medarbejder.objects.get(pk=pk)
else:
medarbejder = self.medarbejder
args = {'medarbejder': medarbejder}
return render(self, 'evalsys/medarbejder/detail.html', args)
def get_context_data(self, **kwargs):
context = super(MedarbejderDetailView, self).get_context_data(**kwargs)
context['eval_list'] = Evaluering.objects.all()
context['fag_list'] = Fag.objects.all()
context['ma'] = Medarbejder.objects.get(pk=self.kwargs.get('pk'))
context['instruktør'] = User.objects.get(username=self.request.user)
return context
def post(self, request, pk):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
print(form.cleaned_data)
instance = form.save(commit=False)
instance.instruktør = request.user
instance.ma = self.object
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
item = form.save()
self.pk = item.pk
return super(MedarbejderDetailView, self).form_valid(form)
def form_invalid(self, form):
return super(MedarbejderDetailView, self).form_invalid(form)
def get_success_url(self):
return reverse_lazy("evalsys:view_evaluering_with_pk", kwargs={'pk': self.pk})
URLs
path('se_alle_evalueringer/<int:pk>', views.MedarbejderEvalDetailView.as_view(), name="view_evaluering_with_fag"),
I know I'm calling the function "view_evaluering_with_fag", so it is because I'm not calling my Bokeh function "evalgraph"?
Didn't know folks couldn't use this as a way to ask follow up questions when we're researching the same question.
Anyhow, I am so about to make your day! With A LOT of trial and error (I've only been coding with Python and Django for a month with no real coding background except Java back in 1999) I got bokeh to render from detailview. It appears the trick is to get stuff under the get_context_data function. I don't know how I came to this conclusion, but I figured the script and div context weren't making their way to the render so I was trying to get them into context. As you'll see below, I put script and div as context['script']=script and context['div']=div. My situation looks a bit simpler, I'm just parsing a bike data file and plotting the data, but hopefully this gets you on your way if you're still trying to make this work.
class FitFileDetailView(DetailView):
model = FitFiles
def get_context_data(self, **kwargs):
model = FitFiles
ff = FitFiles.objects.get(pk=self.kwargs.get('pk'))
ffile = ff.fitfiles.path
fitfile2 = FitFile(ffile)
while True:
try:
fitfile2.messages
break
except KeyError:
continue
workout2 = []
for record in fitfile2.get_messages('record'):
r2 = {}
# Go through all the data entries in this record
for record_data in record:
r2[record_data.name] = record_data.value
workout2.append(r2)
df2 = pd.DataFrame(workout2)
df2['time']=(df2['timestamp'] - df2['timestamp'].iloc[0]).astype('timedelta64[s]')
#Bokeh code
df2 = pd.DataFrame(workout2)
df2['time']=(df2['timestamp'] - df2['timestamp'].iloc[0]).astype('timedelta64[s]')
p2 = figure(x_axis_label='time', y_axis_label='watts', tools="", plot_width=1000, plot_height=500)
p2.line(df2['time'], df2['power'])
p2.line(df2['time'], df2['heart_rate'], color='red')
script, div = components(p2)
context = super(FitFileDetailView, self).get_context_data(**kwargs)
context['script']=script
context['div']=div
return context
I am writing a test for a View where I update context to pass additional information to the template.
Problem
In writing the test, I'm having trouble accessing context from the RequestFactory.
Code
View
class PlanListView(HasBillingRightsMixin, ListView):
"""Show the Plans for user to select."""
headline = "Select a Plan"
model = Plan
template_name = "billing/plan_list.html"
def get_context_data(self, *args, **kwargs):
context = super(PlanListView, self).get_context_data(**kwargs)
context.update({
"customer": self.get_customer()
})
return context
Test
class TestPlanListView(BaseTestBilling):
def setUp(self):
super(TestPlanListView, self).setUp()
request = self.factory.get('billing:plan_list')
request.user = self.user
request.company_uuid = self.user.company_uuid
self.view = PlanListView()
self.view.request = request
self.response = PlanListView.as_view()(request)
def test_get_context_data(self, **kwargs):
context = super(self.view, self).get_context_data(**kwargs)
context.update({"customer": self.view.get_customer()})
self.assertEqual(
self.view.get_context_data(),
context
)
Question
How can I test the view's get_context_data() method?
Using a test client gives you access to your context.
def test_context(self):
# GET response using the test client.
response = self.client.get('/list/ofitems/')
# response.context['your_context']
self.assertIsNone(response.context['page_obj'])
self.assertIsNone(response.context['customer']) # or whatever assertion.
.....
If you don't want to test the full browser behavior you could use the RequestFactory instead. This factory provides a request instance that you can pass to your view. The benefit in my case was that I can test a single view function as a black box, with exactly known inputs, testing for specific outputs. Just as described in the docs.
class TestView(TemplateView):
template_name = 'base.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context = {'1': 11337}
return context
# ...
def test_context(self):
factory = RequestFactory()
request = factory.get('/customer/details')
response = TestView.as_view()(request)
self.assertIsInstance(response.context_data, dict)
self.assertEqual(response.context_data['1'], 1337)
I'm overriding my save method because I want to resize my image. My problem is obvious in this code, what happens after I save my data is that django starts an endless loop. Of course is because I'm checking if self.vertical is set or not (and is always set). Save method is always called.
I could solve this problem checking if my self.id is none, but the code will work only for new entries, if the user tries to update, nothing changes because the id is not null.
I also tried to pop an false argument to kwargs and after saving, set true, but also didnt work. Someone has an idea how can I solve this?
PS: get_thumbnail is a method of sorl thumbnail plugin.
def save(self, *args, **kwargs):
if self.vertical_poster:
super(Movie, self).save(*args, **kwargs)
resized_vertical = get_thumbnail(self.vertical_poster, "155x240", quality=99)
#save the new resized file
self.vertical_poster.save(resized_vertical.name, ContentFile(resized_vertical.read()), True)
super(Movie, self).save(*args, **kwargs)
Any idea or sample code will be appreciated! Thanks
You can check the image whether it is already in your desired dimensions. I think you can use https://pypi.python.org/pypi/dimensions to make it simple
def save(self, *args, **kwargs):
if self.vertical_poster:
import dimensions
# Set desired dimensions
width = 155
height = 240
image_info = dimensions.dimensions(self.vertical_poster)
if image_info[0] != width and image_info[1] != height:
dimension = '%dx%d' % (width, height)
resized_vertical = get_thumbnail(self.vertical_poster, dimension, quality=99)
#save the new resized file
self.vertical_poster.save(resized_vertical.name, ContentFile(resized_vertical.read()), True)
super(Movie, self).save(*args, **kwargs)
Hope it helps.
If you use ImageField you can do it like this:
def save(self, *args, **kwargs):
if self.vertical_poster.x != 155 or self.vertical_poster.y != 240:
resized_vertical = get_thumbnail(self.vertical_poster, "155x240", quality=99)
# save the new resized file
self.vertical_poster.save(resized_vertical.name, ContentFile(resized_vertical.read()), True)
super(Movie, self).save(*args, **kwargs)
My solution was not override my save method. I did something different:
I decided to use "django-resized". This plugin is compatible with "sorl-thumbnail" and resizes image origin to specified size on the model field, as below:
from django_resized import ResizedImageField
class Movie(models.Model):
....
vertical_poster = ResizedImageField(upload_to="movies", width=155, height=240)
I hope that can help other people.
Link: https://github.com/un1t/django-resized
You can create a mixin and use wherever you want likewise.
mixins.py
from io import BytesIO
from PIL import Image
class ShrinkImageMixin:
def shrink_image(self, field_name, resize_shape):
img: Image = Image.open(getattr(self, field_name))
img.thumbnail(self.get_shrinked_size(field_name, resize_shape), Image.ANTIALIAS)
image_file = BytesIO()
img.save(image_file, 'jpg')
getattr(self, field_name).file = image_file
def get_shrinked_size(self, field_name, resize_shape):
actual_img_width, actual_img_height = getattr(self, field_name).width, getattr(self, field_name).height
ratio = min(resize_shape[0] / actual_img_width, resize_shape[1] / actual_img_height)
return int(actual_img_width * ratio), int(actual_img_height * ratio)
models.py
from .mixins import ShrinkImageMixin
class Article(ShrinkImageMixin, models.Model):
...
background_image = models.ImageField()
...
def save(self, *args, **kwargs):
self.shrink_image('background_image', (750, 375))
super(Article, self).save()
This will shrink your image down to your desired size maintaining the aspect ratio. You just need to provide the image field name in the model and resize_shpe tuple.
I am facing a weird issue in Openstack webapp where i am trying to refresh the tabbed content, however i end up appending the refreshed tab content to the the HTML DOM.
The flow of the code is simple except that i donot understand how the HTML is being attached to DOM instead of the existing tab.
The code flow is:
foo.get --->TabView.get --> TabView.handle_tabbed_response.
I desire that the tab be updated and not the DOM,
What have i done:
Class foo((tabs.TabView):
tab_group_class = (barTabs)
template_name = 'project/xyz/index.html'
def get(self, request, *args, **kwargs):
######## Business Logic
thelper = business_logic(request)
thelper.add_data(request)
return super(foo, self).get(request, *args, **kwargs)
def get_initial(self):
initial = super(foo, self).get_initial()
return initial
class TabView(generic.TemplateView):
"""
A generic class-based view for displaying a :class:`horizon.tabs.TabGroup`.
This view handles selecting specific tabs and deals with AJAX requests
gracefully.
.. attribute:: tab_group_class
The only required attribute for ``TabView``. It should be a class which
inherits from :class:`horizon.tabs.TabGroup`.
"""
tab_group_class = None
_tab_group = None
def __init__(self):
if not self.tab_group_class:
raise AttributeError("You must set the tab_group_class attribute "
"on %s." % self.__class__.__name__)
def get_tabs(self, request, **kwargs):
""" Returns the initialized tab group for this view. """
if self._tab_group is None:
self._tab_group = self.tab_group_class(request, **kwargs)
return self._tab_group
def get_context_data(self, **kwargs):
""" Adds the ``tab_group`` variable to the context data. """
context = super(TabView, self).get_context_data(**kwargs)
try:
tab_group = self.get_tabs(self.request, **kwargs)
context["tab_group"] = tab_group
# Make sure our data is pre-loaded to capture errors.
context["tab_group"].load_tab_data()
except Exception:
exceptions.handle(self.request)
return context
def handle_tabbed_response(self, tab_group, context):
"""
Sends back an AJAX-appropriate response for the tab group if
required, otherwise renders the response as normal.
"""
if self.request.is_ajax():
if tab_group.selected:
return http.HttpResponse(tab_group.selected.render())
else:
return http.HttpResponse(tab_group.render())
return self.render_to_response(context)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.handle_tabbed_response(context["tab_group"], context)
def render_to_response(self, *args, **kwargs):
response = super(TabView, self).render_to_response(*args, **kwargs)
# Because Django's TemplateView uses the TemplateResponse class
# to provide deferred rendering (which is usually helpful), if
# a tab group raises an Http302 redirect (from exceptions.handle for
# example) the exception is actually raised *after* the final pass
# of the exception-handling middleware.
response.render()
return response
Please help.
views.py
class PaginatorView(_LanguageMixin, ListView):
context_object_name = 'concepts'
#some custom functions like _filter_by_first_letter
def get_queryset(self):
# some logic here ...
all_concepts = self._filter_by_letter(self.concepts, letters, startswith)
#letters and startswith are obtained from the logic above
print all_concepts
return all_concepts
def get_context_data(self, **kwargs):
context = super(PaginatorView, self).get_context_data(**kwargs)
print context[self.context_object_name]
context.update({
'letters': [(l[0], self._letter_exists(context[self.context_object_name], l)) for l in self.all_letters],
'letter': self.letter_index,
'get_params': self.request.GET.urlencode(),
})
return context
The print all_concepts statement prints all my concepts correctly. So everything until here works just fine. Then, I return all_concepts.
Shouldn't at this point, all_concepts being added to the context, under the key specified by context_object_name? i.e., context['concepts'] should be populated with all_concepts?
If so,the print statement inside get_context_data prints nothing. Which suggests me that the context was not updated.
When I previously used a DetailView, the get_object function was updating the context referenced by context_object_name correctly.(i.e. context[context_object_name] was populated with the object returned by get_object) Shouldn't get_queryset do the same for the ListView?
_LanguageMixin is also defined in views.py, but it is not so relevant for my problem. Just included it here for you to see
class _LanguageMixin(object):
def dispatch(self, request, *args, **kwargs):
self.langcode = kwargs.pop("langcode")
self.language = get_object_or_404(Language, pk=self.langcode)
return super(_LanguageMixin, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(_LanguageMixin, self).get_context_data(**kwargs)
context.update({"language": self.language,
"languages": Language.objects.values_list('code',
flat=True)})
return context
[EDIT1]
if instead I do save all_concepts i.e. self.all_concepts=... and then I use self.all_concepts instead of context[self.contex_object_name], everything works fine.
[EDIT2]
I never instantiate the PaginatorView. It's only for extending purposes. Down here you can see how I extend it. self.concepts helps me to find all_concepts in the get_queryset of the parent class(PaginatorView)
class AlphabeticView(PaginatorView):
template_name = "alphabetic_listings.html"
model = Property
def get_queryset(self):
self.concepts = (
self.model.objects.filter(
name='prefLabel',
language__code=self.langcode,
)
.extra(select={'name': 'value',
'id': 'concept_id'},
order_by=['name'])
.values('id', 'name')
)
super(AlphabeticView, self).get_queryset()
The print statement in get_context_data is printing empty because the variable context_object_name is empty. You should try print context[self.context_object_name]
EDIT: In response to your correction, try
print context[self.get_context_object_name(self.get_queryset())]
get_context_object_name docs
EDIT 2: In response to your second edit, the reason its is printing 'None' is because you aren't returning from the get_queryset method of AlphabeticView. Change the last line in that method to
return super(AlphabeticView, self).get_queryset()