How can i have aspect oriented programming thing in django python - python

I am using django and i have the very complex matrix of permissions . Suppose i have 10 set of permission groups like
Permissions = [basic, medium, advanced , very advanced , admin , superadmin , public , etc]
The other is thing is i have 10 Models and all models have different view, edit permissions for each group so in whole the rules becomes in asort of marix of 10 X 10 and more.
I have all the code now in place without permissions. I really don't want to touch the current code at all places for permission checking.
I need the permission checking at following places
The permssions will be based on logged on user permission group
In templates where we will show edit,insert,delete button based on permissions
In views before showing it , a check is made if user can view/update/delete it
Is there any way that before showing any of the view for delete,update,insert the system automatically checks from the permission matrix without writing any code in the view.
Then only thing left will be in the templates that can write if required

I believe you are simply looking for the permissions variable which is provided by Django within the templates
https://docs.djangoproject.com/en/1.5/topics/auth/default/#permissions
{% if perms.foo %}
<p>You have permission to do something in the foo app.</p>
{% if perms.foo.can_vote %}
<p>You can vote!</p>
{% endif %}
{% if perms.foo.can_drive %}
<p>You can drive!</p>
{% endif %}
{% else %}
<p>You don't have permission to do anything in the foo app.</p>
{% endif %}
Example middleware:
from django import http
class PermissionMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
# you should somehow derive this from the view function and/or view args/kwargs
your_object = SomeThing.objects.get(...)
if not request.user.has_perm('name_of_your_object.permission'):
return http.HttpResponseForbidden()

In views, you can use permission decorators!
They look like this:
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
...
Or this, for more than a login, i.e. permissions:
from django.contrib.auth.decorators import permission_required
#permission_required('polls.can_vote')
def my_view(request):
...
You can also define your own decorators, if you need more complex scenarios (these are the ones I made for myself):
from django.contrib.auth.decorators import user_passes_test
from django.core.exceptions import PermissionDenied
def group_required(*group_names):
"""Requires user membership in at least one of the groups passed in."""
def in_groups(u):
if u.is_authenticated:
if u.groups.filter(name__in=group_names).exists() | u.is_superuser:
return True
return False
return user_passes_test(in_groups)
def superuser_only(function):
"""Limit view to superusers only."""
def _inner(request, *args, **kwargs):
if not request.user.is_superuser:
raise PermissionDenied
return function(request, *args, **kwargs)
return _inner
def has_group(user, group_name):
return user.groups.filter(name=group_name).exists()
...and use them like this:
#login_required
#group_required('A', 'B')
def my_view(request):
....
For use in templates, you can define your own template tags:
from django import template
register = template.Library()
#register.filter
def multiply(value, arg):
return (value * arg)

Related

Show valid page numbers when the current page number is out of range

Have an issue with the default paginated ListView in Django is that when I access a list view with a page number that is out of range, which can typically happen when I refresh the last page while some objects are deleted so the total number of pages is decreased, Django will raise a 404 error.
What I want to do in that case is to show a page with a warning message telling the user the page number is not valid any more, and pagination links which can lead the user the the valid page.
So here is my code
from django.views.generic import ListView
from django.shortcuts import render
from django.http import Http404
class MyListView(ListView):
def get(self, request, *args, **kwargs):
try:
return super().get(request, *args, **kwargs)
except Http404:
return render(request, 'pagenum_out_of_range.html', status=404)
model = MyModel
context_object_name = 'my_models'
template_name = 'my_models_list.html'
paginate_by = 20
and pagenum_out_of_range.html
{% extends my_project_base_template %}
{% block body %}
<p>Invalid page number and there's no result here. Please try somewhere else.</p>
{% for object in page_obj.paginator.page_range %}
<a class="page-link" href="?page={{ forloop.counter }}">{{ forloop.counter }}</a>
{% endfor %}
{% endblock %}
But it doesn't work (no links are shown). I think it's because I didn't pass the context so I changed the last line above to
return render(request, 'pagenum_out_of_range.html', status=404,
context=self.get_context_data())
However the default 404 error comes again.
I'm quite confused about this. What's the correct approach?
UPDATE
Really appreciate your suggestions and I did more research and debug on this.
I tried overriding some more methods mentioned in ListView method flowchart to see if I can change the program behavior or grap some intermediate results.
class MyListView(ListView):
# ...
def get_context_data(self, **kwargs):
print('>>>> get_context_data called')
r = super().get_context_data(**kwargs)
print('>>>> get_context_data return:', r)
return r
def get_template_names(self):
print('>>>> get_template_names called')
r = super().get_template_names()
print('>>>> get_template_names return:', r)
return r
def render_to_response(self, context, **response_kwargs):
print('>>>> render_to_response called')
r = super().render_to_response(context, **response_kwargs)
print('>>>> render_to_response return:', r)
return r
When I refresh the page I can see the following output
>>>> get_context_data called
>>>> get_context_data called
So seems the get_context_data is interrupted by some exception and that exception is Http404 raised from MultipleObjectMixin.paginate_queryset as I traced. I guess that's where I should patch.
My concern is I cannot find any official documentation about paginate_queryset so I'm not quite sure if that's the proper way.
I think you can do that by overriding the get_template_names method. Also, by doing that way I don't think you need to override the get method.
class MyListView(ListView):
model = MyModel
context_object_name = 'my_models'
default_template_name = 'my_models_list.html'
error_template_name = 'pagenum_out_of_range.html'
paginate_by = 20
def get_template_names(self):
if self.request.status_code == 404:
return [self.error_template_name]
else:
return [self.default_template_name]

How to redirect from a view to another view In Django

This is a view written for my posts app in Django. The problem is that after filling the update form and submitting it happens successfully. But it creates confusion for the user because the same HTML page is there and how can I redirect into the updated object?
def post_update(request,id=None):
instance=get_object_or_404(Post,id=id)
if instance.created_user != request.user.username :
messages.success(request, "Post owned by another user, You are having read permission only")
return render(request,"my_blog/denied.html",{})
else :
form=PostForm(request.POST or None,request.FILES or None,instance=instance)
if form.is_valid():
instance=form.save(commit=False)
instance.save()
context={ "form":form,
"instance":instance }
return render(request,"my_blog/post_create.html",context)
As already suggested by #mdegis you can use the Django redirect function to redirect to another view or url.
from django.shortcuts import redirect
def view_to_redirect_to(request):
#This could be the view that handles the display of created objects"
....
perform action here
return render(request, template, context)
def my_view(request):
....
perform form action here
return redirect(view_to_redirect_to)
Read more about redirect here and here
You can pass positional or keyword argument(s) to the redirect shortcut using the reverse() method and the named url of the view you're redirecting to.
In urls.py
from news import views
url(r'^archive/$', views.archive, name='url_to_redirect_to')
In views.py
from django.urls import reverse
def my_view(request):
....
return redirect(reverse('url_to_redirect_to', kwargs={'args_1':value}))
More about reverse Here
You can use redirect from http shortcuts.
from django.shortcuts import redirect
def my_view(request):
...
object = MyModel.objects.get(...)
return redirect(object) #or return redirect('/some/url/')
Here is the link to official docs.
To redirect from a view to another view, you need to give the conbination of the app name "myapp", colon ":" and the view name "dest_view" which is set in the path in "myapp/urls.py" as shown below. And, you don't need to modify the path in "myapp/urls.py" if you pass data with session with request.session['key'] as shown below:
# "myapp/views.py"
from django.shortcuts import render, redirect
def redirect_view(request):
# Here
request.session['person'] = {'name': 'John', 'age': 27}
# Here
return redirect("myapp:dest_view")
def destination_view(request):
return render(request, 'myapp/index.html', {})
You need to give the view name "dest_view" to path() in "myapp/urls.py" as shown below:
# "myapp/urls.py"
from django.urls import path
from . import views
app_name = "myapp"
urlpatterns = [ # This is view name
path('dest/', views.destination_view, name="dest_view")
]
Then, this is Django Template:
# "myapp/index.html"
{{ request.session.person.name }} {# John #}
{{ request.session.person.age }} {# 27 #}
from django.urls import reverse
def my_view(request):
....
return redirect(reverse('url_to_redirect_to', kwargs={'args_1':value(object.id for specific id)}))

Redirecting Django built-in login View to a URL taking user's pk as argument

I am trying to redirect to a URL taking user's pk as argument after successful log-in using Django's built-in login view.
Instead of dynamic {{ next }} variable in my login.html I have a generic landing view of logged-in users;
<input type="submit" value="login" />
<input type="hidden" name="next" value="{% url 'userredirect' %}" />
In my urls.py I have;
url(r'^users/', views.users, name='userredirect'),
url(r'^(?P<pk>\d+)/', UserHome.as_view(), name='userhome'),
and in my views.py I have
#login_required
def users(request):
url = reverse('userhome', kwargs={'pk':request.user.id})
return HttpResponseRedirect(url)
What I am doing here is redirect to a detail view that I have named UserHome on the user model after successful login using 2 redirects as I do not know of a way to redirect to UserHome directly (it takes user's pk as argument). It works and I indeed get redirected to the user's homepage when checking via the browser.
Reference;
The "next" parameter, redirect, django.contrib.auth.login
But when running the below test
def test_page_redirects_to_user_home_on_login(self):
"""
Test to assure that the login page redirects to the user's
home page
"""
username = "someusername"
password = "somepassword"
user = User.objects.create_user(username=username,
password=password)
user.save()
response = self.client.post(reverse("userlogin"),
{"username":username,
"password":password},
follow=True)
assert response.path == self.client.get(reverse("userhome",
kwargs={"pk":user.id}
)
)
I get the below failure
AttributeError: 'HttpResponseNotFound' object has no attribute 'path'
It seems the test client gets no page. Would it be that I am using the userredirect view simply for redirecting and the client do not go ahead and get the UserHome class view to its context.
I'm a newbie to Django/Python. Someone please sort this out for me :).
I look forward either to a way where I can redirect directly from the template for login view to UserHome or a way to rewrite my test.
Hard to say without much more insight in your project. Here are a few possibilities and such.
Response has no path
response indeed has no path, you probably wanted this:
assert response.wsgi_request.path == reverse("userhome", kwargs={"pk":user.id})
Include next in your test
You're simulating data from the login form, but you're omitting the next field.
Add it to the POSTed data:
{"username":username,
"password":password,
"next": '/users/',}
Take a look what's in the response
It might help to see what's in the response in your test. For example:
print(response.redirect_chain)
Perhaps you're not even reaching the login page?
Are you missing LOGIN_URL in your settings.py?
LOGIN_URL = '/login/'
Without it, you'll be redirected to '/accounts/login/', which might be the 404 you're seeing.
Finaly - why? :)
Perhaps you have some special use case, but I'd usually read user's id (a.k.a. pk) from request.user. That way I (for example) can't access example.com/<your_id> and access your homepage. Of course, that might be just what you intend. In that case I'd still have a separate URL for current user, it will probably pay off later. Something like this:
...
url(r'^/', UserHome.as_view(), name='userhome'),
url(r'^(?P<pk>\d+)/', UserHome.as_view(), name='userhome'),
...)
class UserHome(DetailView): # also protect with some LoginRequiredMixin
model = User
def get_object(self, queryset=None):
if queryset is None:
queryset = self.get_queryset()
id = self.kwargs.get('pk', self.request.user.id)
return queryset.filter(id=id).get()
First things first: The error you get is because the line
response = self.client.post(reverse("userlogin"),
{"username":username,
"password":password},
follow=True)
raises a 404 error, hence resonse is a HttpResponseNotFound.
Before testing anything else is it a good practice to first test that your request was successful. Something along the line of:
self.assertEqual(response.status_code, 200)
Also, you are hard-coding url's which goes against DRY and is often the source for trouble (maybe it is the case here).
It would be better to name all your urls:
url(r'^users/', views.users, name='user_redirect'),
and then use this in your template
<input type="hidden" name="next" value="{% url 'user_redirect' %}" />
and this in your view
from django.core.urlresolvers import reverse
#login_required
def users(request):
url = reverse('userhome', kwargs={'pk': request.user.id})
return HttpResponseRedirect(url)
And finally, you are taking an unnecessary step with the redirect. Assuming UserHome is a DetailView on User, you could have this code:
##urls.py
url(r'^users/', UserHome.as_view(), name='userhome')
##views.py
from django.views.generic import DetailView
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
class UserHome(DetailView):
model = User
def get_object(self, queryset=None):
return self.request.user
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(UserHome, self).disatch(*args, **kwargs)
This would also ensure that no user accesses another user's "userhome".
Doing all this should help you find what went wrong with your code. Good luck!

Django Class Views and Reverse Urls

I have a good many class based views that use reverse(name, args) to find urls and pass this to templates. However, the problem is class based views must be instantiated before urlpatterns can be defined. This means the class is instantiated while urlpatterns is empty leading to reverse throwing errors. I've been working around this by passing lambda: reverse(name, args) to my templates but surely there is a better solution.
As a simple example the following fails with exception:
ImproperlyConfigured at xxxx
The included urlconf mysite.urls doesn't have any patterns in it
mysite.urls
from mysite.views import MyClassView
urlpatterns = patterns('',
url(r'^$' MyClassView.as_view(), name='home')
)
views.py
class MyClassView(View):
def get(self, request):
home_url = reverse('home')
return render(request, 'home.html', {'home_url':home_url})
home.html
<p><a href={{ home_url }}>Home</a></p>
I'm currently working around the problem by forcing reverse to run on template rendering by changing views.py to
class MyClassView(View):
def get(self, request):
home_url = lambda: reverse('home')
return render(request, 'home.html', {'home_url':home_url})
and it works, but this is really ugly and surely there is a better way. So is there a way to use reverse in class based views but avoid the cyclic dependency of urlpatterns requiring view requiring reverse requiring urlpatterns...
EDIT:
I'm using this as so:
views.py
def sidebar_activeusers(cls):
sidebar_dict = {'items' = []}
qs = models.random.filter.on.users
for user in qs:
item = {
'value': user.name,
'href': reverse('user_profile', args=[hash_id(user.id)])}
sidebar = loader.get_template('sidebar.html')
cls.base_dict['sidebar_html'] = sidebar.render(Context(sidebar_dict))
return cls
#sidebar_activeusers
class MyView1(View):
base_dict = {}
...
#other_sidebar_that_uses_same_sidebar_template
class MyView2(View):
basically I'd like to use the same sidebar template for a few different content types. Although the models displayed in the sidebar will be arbitrary the format will always be the same.
For your example MyClassView, it's ok to use reverse
class MyClassView(View):
def get(self, request):
home_url = reverse_lazy('home')
return render(request, 'home.html', {'home_url': home_url},
The reverse method is not called when the class is defined -- It is only called when the request is processed and the get method is called, so there shouldn't be an error. I have tested the example above and it works fine.
However, When you use your sidebar_activeusers decorator, the reverse call occurs before the url conf is loaded. In cases like these, you can use reverse_lazy. Here's an example of reverse_lazy in action:
from django.core.urlresolvers import reverse_lazy
class MyOtherClassView(View):
home_url = reverse_lazy('home')
def get(self, request):
return render(request, 'home.html', {'home_url':self.home_url})
This time, home_url is set when the class is defined, so reverse_lazy is required instead of reverse, because the url conf hasn't loaded yet.
Your home url can be accessed directly in your template by using declaring {% url 'home' %}. It's a standard way to access your named urls in your urls.py file.
You do not have to send the home_url variable to your template explicitly in your class-based view function.
In other words, in your home.html file:-
<p>Home</p>
will do.

Why is django.test.client.Client not keeping me logged in

I'm using django.test.client.Client to test whether some text shows up when a user is logged in. However, I the Client object doesn't seem to be keeping me logged in.
This test passes if done manually with Firefox but not when done with the Client object.
class Test(TestCase):
def test_view(self):
user.set_password(password)
user.save()
client = self.client
# I thought a more manual way would work, but no luck
# client.post('/login', {'username':user.username, 'password':password})
login_successful = client.login(username=user.username, password=password)
# this assert passes
self.assertTrue(login_successful)
response = client.get("/path", follow=True)
#whether follow=True or not doesn't seem to work
self.assertContains(response, "needle" )
When I print response it returns the login form that is hidden by:
{% if not request.user.is_authenticated %}
... form ...
{% endif %}
This is confirmed when I run ipython manage.py shell.
The problem seems to be that the Client object is not keeping the session authenticated.
Just happened to me when retesting an app that has been working and forgotten for some months.
The solution (apart from updating to Django 1.2) is Patch #11821. In short, Python 2.6.5 has some bugfix in the Cookie module, triggering an edge case bug in the test client.
FWIW, an update to Django 1.2 (I was running 1.1.1 before) fixed it. I have no idea what was broken there, considering when I last ran that test suite about 2 weeks ago, it worked great.
I use RequestContext to get the logged in user into the template context.
from django.shortcuts import render_to_response
from django.contrib.auth.decorators import login_required
from django.template import RequestContext
#login_required
def index(request):
return render_to_response('page.html',
{},
context_instance=RequestContext(request))
and in the template
{% if user.is_authenticated %} ... {{ user.username }} .. {% endif %}
This works as expected (I don't get to this page without logging in, and when I get there, the username is present in response.content) when driven through the test client.

Categories

Resources