Unit test on custom django admin template - python

No clue how to deal with this situation.
Recently start unit test with django.
In my project, i have a custom change_list.html to add a button on the admin page.
I'm looking for a unit test which can verify this custom button.
Here is my code in admin.py :
class LocationAdmin(OSMGeoAdmin):
def get_urls(self):
urls = patterns('', url(
r'^import/$',
self.admin_site.admin_view(self.import_process)
))
super_urls = super(LocationAdmin, self).get_urls()
return urls + super_urls
def import_process(self, request):
pass
admin.site.register(Location, LocationAdmin)
This code automaticaly load the template in app/admin/app/app/change_list.html
Which is :
{% extends "admin/change_list.html" %}
{% load staticfiles %}
{% block extrahead %}
<link rel="stylesheet" href="{% static "css/custom_upload.css" %}">
{% endblock %}
{% block object-tools-items %}
{{ block.super }}
<li><a role="button" href="import/" class="btn btn-primary btn-margin-left">Import</a></li>
{% endblock %}
But how about when you want to proove that work's with a unit test?
I'm able to test template when i can use a view and the function render_to_string
Like this :
response = my_view(HttpRequest())
expected_html = render_to_string(
'template.html',
{'arg_key': arg}
)
self.assertEqual(response.content.decode(), expected_html)
But here, i don't figure it out how to call the admin view with the associate template part.
Here a start of what i found to add unit test on admin page.
class MockRequest(object):
pass
class MockSuperUser(object):
def has_perm(self, perm):
return True
request = MockRequest()
request.user = MockSuperUser()
class ModelAdminTests(TestCase):
def setUp(self):
self.site = AdminSite()
def test_admin_import_button_on_location_admin_page(self):
ma = ModelAdmin(Location, self.site)
#Here the correct assert ?
Thank you all.

This might not be a good idea (probably a better alternative: using the test client). But for reference, here is how you can render the changelist_view programmatically directly from the admin method:
from django.contrib.admin import AdminSite, ModelAdmin
from django.test.client import RequestFactory
from django.contrib.admin.templatetags.admin_urls import admin_urlname
from django.shortcuts import resolve_url
class MockUser:
is_active = True
is_staff = True
def has_perm(self, *args):
return True
url = resolve_url(admin_urlname(MyModel._meta, "changelist"))
request = RequestFactory().get(url)
request.user = MockUser()
class MyAdmin(ModelAdmin):
change_list_template = "path/to/custom/template/if/needed"
admin = MyAdmin(Work, AdminSite())
html = admin.changelist_view(request).rendered_content

Related

DeleteView with 2 arguements post and user

I have a delete view with 2 conditions "post" and "user".
The user requirement is fulfilled by self.object.user = self.request.user and post requirement is fulfilled by slug = self.kwargs['slug'](I think this may be the culprit)
Are my views correct?
I am new to python please forgive any silly mistakes.
Views.py
class ProofDelete(LoginRequiredMixin, DeleteView):
model = Proof
def delete(self, *args, **kwargs):
return super().delete(*args, **kwargs)
def get_success_url(self, *args, **kwargs):
slug = self.kwargs['slug']
print(slug)
obj = get_object_or_404(Post, slug=slug)
url_ = obj.get_absolute_url()
user = self.request.user
if user.is_authenticated():
if user in obj.made.all():
obj.made.remove(user)
else:
obj.made.add(user)
return url_
models.py
User = get_user_model()
class Proof(models.Model):
user = models.ForeignKey(User, related_name='proofmade')
post = models.ForeignKey(Post, related_name='proofmade')
made_at = models.DateTimeField(auto_now=True)
image_of_proof= models.ImageField()
proof_ = models.ImageField()
suggestions = models.TextField(max_length=1000)
def __str__(self):
return self.post.title
urls.py
app_name = 'proof'
urlpatterns = [
url(r'^new_proof/(?P<slug>[-\w]+)/$', views.ProofCreate.as_view(), name='new_proof'),
url(r'^proof_delete/(?P<pk>\d+)/$', views.ProofDelete.as_view(),name='proof_delete'),
also tried
url (r'^proof_delete/(?P<slug>[-\w]+)/(?P<pk>\d+)/$', views.ProofDelete.as_view(), name='proof_delete'),
I get the below error. Indicating error in the views
Error Message Click to see Error message
Same error message after scrolling down. Click to open
The answer was in the templates. Adding a forloop after the "if" statement solved the issue. Doing this handles both arguments of the Url. "slug" of "post" model and "pk" of the "proof" model. Without the forloop It was almost impossible to satisfy both arguments of the URL
The correct Url thanks to "Paulo Almeida"
url (r'^proof_delete/(?P<slug>[-\w]+)/(?P<pk>\d+)/$', views.ProofDelete.as_view(), name='proof_delete')
Below is the template
{% if user in post.made.all %}
{% for proof in user.proofmade.all %}
<a href="{% url 'proof:proof_delete' slug=post.slug pk=proof.pk %}">
<img src="{% static 'images/thumbs_up_RED.png' %}" height="25px">
</a><br/>
{% endfor %}
{% else %}
<a href="{% url 'proof:new_proof' slug=post.slug %}">
<img src="{% static 'images/thumbs_up_BLANK.png' %}" height="25px">
</a><br/>
{% endif %}

Filtering Queryset and designing correct urls.py and templates in Django

I am designing a video rendering kids website.
The app has parent categories for eg: Cartoons, Education shows etc. Each category has multiple posts such as Jungle_book,Duck_tales etc for cartoons.
Each post has multiple episodes.
I am using generic views(listview, Detailview) for views.
Here are my Models.py
from django.db import models
from personal.models import Post
class cartoons_post(models.Model):
category= models.ForeignKey(Post, on_delete=models.CASCADE)
title = models.CharField(max_length = 140)
thumbnail=models.ImageField()
date=models.DateField()
def __unicode__(self):
return self.title
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("posts:detail", kwargs={"id": self.id})
class post_episode(models.Model):
post_id= models.ForeignKey(cartoons_post, on_delete=models.CASCADE)
title = models.CharField(max_length = 140)
thumbnail=models.ImageField()
date=models.DateField()
video=models.FileField()
def __unicode__(self):
return self.title
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("posts:detail", kwargs={"id": self.id})
Here is my urls.py
from django.conf.urls import url, include
from django.views.generic import ListView, DetailView
from cartoons.models import cartoons_post,post_episode
from django.contrib.auth.decorators import login_required
urlpatterns = [
url(r'^$', login_required(ListView.as_view(
queryset=cartoons_post.objects.all().order_by("-date")[:10],
template_name="cartoons/cartoons.html"))),
url(r'^(?P<pk>\d+)$', login_required(ListView.as_view(
queryset=post_episode.objects.filter(post_id=1).order_by("-date"),
template_name="cartoons/post.html"))),
url(r'^(?P<pk>\d+\d+)$',login_required(DetailView.as_view(
model = post_episode,
template_name="cartoons/post_episode.html"))),
]
Here are my three templates
cartoons.html
{% extends "personal/header.html" %}
{% load staticfiles %}
{% block content %}
{% for cartoons_post in object_list %}
<img src= "{{cartoons_post.thumbnail.url}}" width="200" height="220">
{% endfor %}
{% endblock %}
post.html
{% extends "personal/header.html" %}
{% load staticfiles %}
{% block content %}
{% for post_episode in object_list %}
<img src= "{{post_episode.thumbnail.url}}" width="200" height="220">
{% endfor %}
{% endblock %}
post_episode.html
{% extends "personal/header.html" %}
{% load staticfiles %}
{% block content %}
<div class="container-fluid_cartoons">
<h3>{{post_episode.title}}</h3>
<video width="850" height="500" controls>
<source src="{{post_episdoe.video.url}}" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
{% endblock %}
Issues
- Only cartoons.html is working fine.
Once I click on an individual post the relevant post.html shows all the episode uploaded rather than episodes pertaining to individual post only.
Once I click on an episode, nothing happens.
You should consider creating views.py file rather than using generic views in urls.py. I think issue is with following line
url(r'^(?P<pk>\d+)$',login_required(ListView.as_view(queryset=post_episode.objects.filter(post_id=1).order_by("-date"),template_name="cartoons/post.html"))),
Here, If my guess is correct, you are trying to pass id of post model from url to view. But to make ListView accept url kwargs (post_id) you have to write another view in views.py. Maybe something like below.
class EpisodeListView(ListView):
template_name = 'cartoons/post.html'
def get_queryset(self):
return post_episode.objects.filter(post_id=self.kwargs['post_id']).order_by("-date")
Then route a url to that view:
from <yourapp.views> import EpisodeListView
from django.contrib.auth.decorators import login_required
url(r'^episodes/(?P<pk>\d+)$',login_required(EpisodeListView.as_view())

Template include tag: Choose another template if it does not exist

I would like to include a different template depending request. For example:
Suppose that request has:
request.country = 'Spain'
request.city = 'Madrid'
I want to include "index.html" view but:
---- If myapp/index_madrid.html exist
{% include "myapp/index_madrid.html" %}
---- elif myapp/index_spain.html exist
{% include "myapp/index_spain.html" %}
---- else go to default version
{% include "myapp/index.html" %}
How can I achieve this behaviour in a transparent way? I mean, I would like to do something like:
{% my_tag_include "myapp/index.html" %}
{% my_tag_include "myapp/another_view.html" with p='xxx' only%}
{% my_tag_include "myapp/any_view.html" with p='sss' a='juan' %}
and achieve the cascading loading that I explained before.
Thanks
One possibility is to implement this kind of logic in the view:
# views.py
from django.template.loader import select_template
class MyView(View):
def get(self, request):
include_template = select_template([
'myapp/index_{}.html'.format(request.city),
'myapp/index_{}.html'.format(request.country),
'myapp/index.html'
])
ctx = {
'include_template': include_template
}
return render(request, 'myapp/base.html', ctx)
# myapp/base.html
{% include include_template %}
select_template() will return the first template in the passed list that exists. The include tag supports including compiled templates beginning from Django 1.7, so this should work fine if you are on 1.7 or above.
Update: Reuse across multiple views
# utils.py
from django.template.loader import select_template
def get_approriate_template(tokens, app_name):
templates = ['{}/index_{}.html'.format(app_name, x) for x in tokens]
templates.append('{}/index.html'.format(app_name))
return select_template(templates)
# views.py
from .utils import get_approriate_template
class MyView(View):
def get(self, request):
tokens = [request.city, request.country]
ctx = {
'include_template': get_appropriate_template(tokens, app_name='myapp')
}
return render(request, 'myapp/base.html', ctx)
Update: Rendering template from a template tag
# templatetags/myapp_extras.py
from django import template
register = template.Library()
#register.simple_tag(takes_context=True)
def my_tag_include(context):
from django.template.loader import select_template
include_template = select_template([
'myapp/index_{}.html'.format(context['request'].city),
'myapp/index_{}.html'.format(context['request'].country),
'myapp/index.html'
])
return include_template.render(context)
# myapp/base.html
{% load myapp_extras %}
{% my_tag_include %}
you can implement a custom template tag to check the existence of the template:
from django import template
register = template.Library()
def template_exists(template_name):
try:
django.template.loader.get_template(template_name)
return True
except template.TemplateDoesNotExist:
return False
register.filter('template_exists', template_exists)
And in your template :
{% if template_exists "myapp/index_"add:request.city|add:".html" %}
{% include "myapp/index_"add:request.city|add:".html" %}
{% elif template_exists "myapp/index_"add:request.country|add:".html" %}
{% include "myapp/index_"add:request.country|add:".html" %}
{% else %}
{% include "myapp/index.html" %}
Edit :
You can do all your logic through the template tag:
from django import template
register = template.Library()
#register.simple_tag(takes_context=True)
def existing_template(context):
request = context["request"]
if django.template.loader.get_template("myapp/index_"+ request.city+".html"):
return "myapp/index_"+ request.city+".html"
elif django.template.loader.get_template("myapp/index_"+ request.country+".html"):
return "myapp/index_"+ request.country+".html"
else:
return "myapp/index.html"
And in your template :
{% include required_template %}
You can do this by writing your own template tag which returns the valid template which exists.
Method-1 Using assignment_tag:
some_app/templatetags/some_app_extras.py
from django import template
from django.template.loader import select_template
register = template.Library()
#register.assignment_tag(takes_context=True)
def get_valid_template(context):
city_template = 'myapp/index_{}.html'.format(context['request'].city)
country_template = 'myapp/index_{}.html'.format(context['request'].country)
index_template = 'myapp/index.html'
templates_list = [city_template, country_template, index_template]
valid_template = select_template(templates_list)
return valid_template.name
In your template:
{% load some_app_extras %}
{% get_valid_template as valid_template %}
{% include valid_template %}
We use assignment tag for it and pass the context to it by passing takes_context=True argument when registering the tag. Since we have the context, we can access the request to get the city and country. After this, we use the select_template() function of django and pass it a list of templates which will return the first template found in the list.
Then in our template, we can use this template tag to get the valid template and use this variable to render the valid template.
Method-2 Using simple_tag:
If you want to do the rendering in 1 line only as you mentioned in the comments, then you can use simple_tag. You don't need to assign the template name in a variable then for the rendering to be done by include built-in template tag later. get_template_tag will automatically display the necessary template since it has been already been rendered in the template tag code.
some_app/templatetags/some_app_extras.py
from django import template
from django.template.loader import select_template
register = template.Library()
#register.simple_tag(takes_context=True)
def get_valid_template(context):
city_template = 'myapp/index_{}.html'.format(context['request'].city)
country_template = 'myapp/index_{}.html'.format(context['request'].country)
index_template = 'myapp/index.html'
templates_list = [city_template, country_template, index_template]
valid_template = select_template(templates_list)
return valid_template.render(context)
In your template:
{% load some_app_extras %}
{% get_valid_template %}
Finally I solved the problem by creating a custom simple tag, such as:
#register.simple_tag(takes_context=True)
def my_own_include(context, *args):
#I get the request and create a list of possible template views
return select_template(list_of_posible_views).render(context)
On the .html file I would call this tag like:
{% my_own_include 'index.html' %}
Thanks to all of you for your help.

Django request.user in model self function

I want to get current logged user in my model self function.
I tried this.
class My_model(models.Model):
user = models.OneToOneField(User)
image = models.ImageField(upload_to='uploads/',default='uploads/no-img.jpg')
#other fields
def show_photo(self,request):
show_photo = False
if Photo_request.objects.filter(who=request.user,whose=self.user).exists():
my_request = Photo_request.objects.get(who=request.user,whose=self.user)
request_accepted = my_request.accepted
if request_accepted:
show_photo = True
return show_photo
show_photo = property(show_photo)
In my template
{% for profile in profiles %}
{% if profile.show_photo %}
<img src="{{MEDIA_URL}}{{ profile.image }}" alt="image" />
{% endif %}
{% endfor %}
but this function not working. I tried without request parameter and custom id, thats working. Is there any problem in my code?
Write the custom tag:
my_app/templatetags/my_app_tags.py
from django.template import Library
register = Library()
#register.assignment_tag(takes_context=True)
def show_photo(context):
request = context['request']
profile = context['profile']
return profile.show_photo(request) # Instead of passing request I suggest to pass request.user here
Use it in template by loading the template_tags:
{% load my_app_tags %}
{% for profile in profiles %}
{% show_photo as show %}
{% if show %}
<img src="{{MEDIA_URL}}{{ profile.image }}" alt="image" />
{% endif %}
{% endfor %}
I used current_thread function in threading package.
Add a new middleware in utils/request_utiles.py file as following:
utils/request_utiles.py
from threading import current_thread
from django.utils.deprecation import MiddlewareMixin
_requests = {}
def get_current_request():
t = current_thread()
if t not in _requests:
return None
return _requests[t]
class RequestMiddleware(MiddlewareMixin):
def process_request(self, request):
_requests[current_thread()] = request
Add the middleware in settings.py file
settings.py
MIDDLEWARE = [
...
'utils.request_utils.RequestMiddleware',
]
Use get_current_request() function in any models.
In your code, you can use as following:
from utils.request_utils import get_current_request
class My_model(models.Model):
user = models.OneToOneField(User)
image = models.ImageField(upload_to='uploads/',default='uploads/no-img.jpg')
...
def show_photo(self):
request = get_current_request() # you can get current request in here.
show_photo = False
if Photo_request.objects.filter(who=request.user,whose=self.user).exists():
my_request = Photo_request.objects.get(who=request.user, whose=self.user)
request_accepted = my_request.accepted
if request_accepted:
show_photo = True
return show_photo
show_photo = property(show_photo)

Include template form to django admin

Is it possible to include model form template in django admin as follows?
models.py
class Customer(models.Model):
name = models.CharField(max_length=20)
designation = models.CharField(max_length=20)
gender = models.BooleanField()
forms.py
class CustomerForm(forms.ModelForm)
gender = forms.TypedChoiceField(
choices=GENDER_CHOICES, widget=forms.RadioSelect(renderer=HorizontalRadioRenderer), coerce=int, )
class Meta:
model = Customer
template.html
<form action="{% url 'verinc.views.customerView' %}" method="POST">{% csrf_token %}
{{ form.as_p }}
<input id="submit" type="button" value="Click" /></form>
views.py
def customerView(request):
if request.method == 'POST':
form = CustomerForm(request.POST)
else:
form = CustomerForm()
return render_to_response('myapp/template.html', {'form' : form,})
admin.py
class CustomerInline(admin.StackedInline)
model= Customer
form = CustomerForm
template = 'myapp/template.html
When I view the form in url (localhost/myapp/customer) it displays all the fields correctly. But when I view it in admin page it displays only the submit button in the block. My requirement is to view the form using templates in admin page, so that i could use some AJAX script for further process. Any help is most appreciated.
Well , Its not possible . But you can implement like this :
You should create file called admin_views.py in your app customer.
Then add url in your urls.py like
(r'^admin/customer/customerView/$', 'myapp.customer.admin_views.customerView'),
Write your view implementation inside admin_views.py like :
from myapp.customer.models import Customer
from django.template import RequestContext
from django.shortcuts import render_to_response
from django.contrib.admin.views.decorators import staff_member_required
def customerView(request):
return render_to_response(
"admin/customer/template.html",
{'custom_context' : {}},
RequestContext(request, {}),
)
customerView = staff_member_required(customerView)
And inside your admin template extend base_site.html like this one :
{% extends "admin/base_site.html" %}
{% block title %}Custmer admin view{% endblock %}
{% block content %}
<div id="content-main">
your content from context or static / dynamic...
</div>
{% endblock %}
Thats it . hit that new url after admin login . Note Not Tested :) .

Categories