In a mini blog app, I want to create a delete function, so that the owner of the blog can delete his entries (and only his entries).
I guess that the only methods for doing do, is using a form.
Though my the deletion code seems clear and correct, it doesn't work.
My code:
def delete_new(request,id):
u = New.objects.get(pk=id).delete()
if request.method == 'POST':
form = DeleteNewForm(request.POST)
form.u.delete()
form.save()
return render_to_response('news/deleteNew.html', {
'form': form,
},
context_instance=RequestContext(request))
and in the template:
<a href='/news/delete_new/{{object.id}}/'> Delete</a> <br />
Is this a correct approach? I mean, creating a form for this?
also, the only way to take the blog post associated with the deletion link is having an id as a parameter. Is it right? I mean, maybe any user can type another id, in the url, and delete another entry (eventually not one of his)
You need to use a form, or you're vulnerable to CSRF attacks. You're also deleting the model before you've checked whether the request was a GET or a POST.
Create a simple ModelForm:
from django import forms
from .models import New
class DeleteNewForm(forms.ModelForm):
class Meta:
model = New
fields = []
In your views.py in the same Django app:
from django.shortcuts import render, get_object_or_404
from .forms import DeleteNewForm
from .models import New
def delete_new(request, new_id):
new_to_delete = get_object_or_404(New, id=new_id)
#+some code to check if this object belongs to the logged in user
if request.method == 'POST':
form = DeleteNewForm(request.POST, instance=new_to_delete)
if form.is_valid(): # checks CSRF
new_to_delete.delete()
return HttpResponseRedirect("/") # wherever to go after deleting
else:
form = DeleteNewForm(instance=new_to_delete)
template_vars = {'form': form}
return render(request, 'news/deleteNew.html', template_vars)
In general, for deleting objects you should rather use POST (or DELETE) HTTP methods.
If you really want to use HTTP GET for your example, here is what you need to fix:
If you have url pointing to some url like yours: <a href='/news/delete_new/{{object.id}}/'> Delete</a> then you can simply write view that will check if object belongs to logged in user and delete this entry if yes, like in code you have already written:
def delete_new(request,id):
#+some code to check if New belongs to logged in user
u = New.objects.get(pk=id).delete()
To check if New objects belogs to some user you need to create realation between User and New (like created_by = models.ForeignKey(User) in New model).
You can get logged in user this way: request.user
I hope I got your point correctly and my answer helps you somehow.
PS: You can also consider using {% url %} tag instead of writing urls directly in your templates.
Related
I have a project with a Post model, that is basic posts. I want to create a link on each post page to be able to delete that post (with appropriate security).
There are a few questions on this on stack overflow, but I can't seem to find a complete, workable answer (I am using Django 1.7) that doesn't throw up errors when I implement it.
I have been able to implement a delete function which works ok, but need to add a POST form with CSRF token for validation, and also check that the user deleting it is the one that created it. I can't seem figure out how to add these two in.
So far, in my views.py:
def delete(request, id):
post = Post.objects.filter(pk=id).delete()
return HttpResponseRedirect(reverse('posts.views.all_posts'))
In urls.py:
url(r'^delete/(?P<id>\d+)/$','posts.views.delete'),
In html:
Delete
This all works, but there is no security - so appreciate guidance on how to add a form and checking.
Also, I've seen an answer that uses DeleteView, but couldn't get that one to work either.
Indeed, using a GET method to delete your objects makes you vulnerable to CSRF attacks.
DeleteView only deletes on POST, and shows a confirmation page on GET.
Your code should look something like this in views.py:
from django.views.generic import DeleteView
class PostDelete(DeleteView):
model = Post
success_url = reverse_lazy('posts.views.all_posts')
In urls.py:
url(r'^delete/(?P<pk>\d+)/$', PostDelete.as_view(),
name='entry_delete'),
Your form (without using a confirmation template. There is an example of confirmation template in the docs):
<form action="{% url 'entry_delete' object.pk %}" method="post">
{% csrf_token %}
<input type="submit" value="Delete" />
</form>
If you are not using a confirmation template, make sure to point the form's action attribute to the DeleteView (this is why).
To ensure the user deleting the post is the user that owns it, I like to use mixins. Assuming your Post model has a created_by foreign key pointing to User, you could write a mixin like:
from django.core.exceptions import PermissionDenied
class PermissionMixin(object):
def get_object(self, *args, **kwargs):
obj = super(PermissionMixin, self).get_object(*args, **kwargs)
if not obj.created_by == self.request.user:
raise PermissionDenied()
else:
return obj
Finally, your DeleteView should inherit from this mixin:
class PostDelete(PermissionMixin, DeleteView):
model = Post
success_url = reverse_lazy('posts.views.all_posts')
In my Django project, I have various users created by Django's built-in authentication system. Each user can create their own instances of the App model. I would like to restrict user access to objects such that users can only view the instances they have created. To do that I have created this view:
#login_required
def appDetail(request, app_id):
try:
app = App.objects.get(pk=app_id)
# Testing if the currently logged in user is
# the same as the user that created the 'app':
if request.user.id == app.user.user.id:
if request.method == 'POST':
form = AppForm(request.POST, instance=app)
if form.is_valid():
edited_app = form.save()
return HttpResponseRedirect('/thanks/')
else:
form = AppForm(instance=app)
# If 'app' does not belong to logged in user, redirect to 'accessdenied' page:
else:
return HttpResponseRedirect('/accessdenied/')
except LeaveApp.DoesNotExist:
raise Http404
return render(request, 'AppDetail.html', {'form':form})
It works, but I'm wondering if there's a more commonly accepted and/or safe way to do this?
This is called row-level permissions and it's a very common problem. See here for all the apps that solve it.
If that particular test is all you need to do, go for a custom solution like yours (though, since it's boilerplate, it's preferable to move it to a decorator). Otherwise, just use an existing app.
I would put the form submission in a different view and write a custom decorator, which you could also use for similar issues.
I would also return a 404 instead of access denied. You might not want to show users that you are protecting something.
There is a decorator called user_passes_test that restricts access to a view based on if the user passes a certain check
from django.contrib.auth.decorators import login_required, user_passes_test
#login_required
#user_passes_test(lambda user: user.username == app.user.user.id)
MyView(request):
...
You can also add in an optional argument for a url to redirect to in the event they fail the check.
Trying to do this from the admin page is also pretty easy, but takes a few extra steps.
Docs Here
I have a requirement here to build a comment-like app in my django project, the app has a view to receive a submitted form process it and return the errors to where ever it came from. I finally managed to get it to work, but I have doubt for the way am using it might be wrong since am passing the entire validated form in the session.
below is the code
comment/templatetags/comment.py
#register.inclusion_tag('comment/form.html', takes_context=True)
def comment_form(context, model, object_id, next):
"""
comment_form()
is responsible for rendering the comment form
"""
# clear sessions from variable incase it was found
content_type = ContentType.objects.get_for_model(model)
try:
request = context['request']
if request.session.get('comment_form', False):
form = CommentForm(request.session['comment_form'])
form.fields['content_type'].initial = 15
form.fields['object_id'].initial = 2
form.fields['next'].initial = next
else:
form = CommentForm(initial={
'content_type' : content_type.id,
'object_id' : object_id,
'next' : next
})
except Exception as e:
logging.error(str(e))
form = None
return {
'form' : form
}
comment/view.py
def save_comment(request):
"""
save_comment:
"""
if request.method == 'POST':
# clear sessions from variable incase it was found
if request.session.get('comment_form', False):
del request.session['comment_form']
form = CommentForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
if request.user.is_authenticated():
obj.created_by = request.user
obj.save()
messages.info(request, _('Your comment has been posted.'))
return redirect(form.data.get('next'))
else:
request.session['comment_form'] = request.POST
return redirect(form.data.get('next'))
else:
raise Http404
the usage is by loading the template tag and firing
{% comment_form article article.id article.get_absolute_url %}
my doubt is if am doing the correct approach or not by passing the validated form to the session. Would that be a problem? security risk? performance issues?
Please advise
Update
In response to Pol question. The reason why I went with this approach is because comment form is handled in a separate app. In my scenario, I render objects such as article and all I do is invoke the templatetag to render the form. What would be an alternative approach for my case?
You also shared with me the django comment app, which am aware of but the client am working with requires a lot of complex work to be done in the comment app thats why am working on a new one.
I dont see the problem with security, except situation when you using cookies for stroring session. The performance depends on what kind of session backand you are using as well. But I cant find the point why are you complicating things!
And I dont thing that touching session in template tag is a good idea at all.
And maybe Take a look at django Comments Framework
Update:
Ok. I cant see the problems in this approach except complication. For example in my project, i'm using ajax to send data and validate it right in the comments view, therefore I do not require to redirect to original page. Other thing is that I pass the initialized Form in article view, so i'm not using templatetags.
Can provide you with my approche for example purposes:
from forms import CommentForm
from models import Comment
from django.http import HttpResponseForbidden, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import simplejson
from etv_persons.person.models import Person
from django.contrib import messages
def create_comment(request,slug):
if request.method != 'POST' or not request.POST or request.user.is_anonymous():
return HttpResponseForbidden('Доступ запрещен')
person = get_object_or_404(Person,slug=slug)
form = CommentForm(data=request.POST)
if form.is_valid():
Comment.objects.create(user_id=request.user.id, person=person,text=form.cleaned_data['text'])
if request.is_ajax():
msg={'msg': 'Cement was send',}
else:
messages.info(request, 'COmment was send.')
else:
if request.is_ajax(): msg={'msg': 'Error.',}
else: messages.info(request, 'Error.')
if request.is_ajax():
return HttpResponse(simplejson.dumps(msg),content_type='application/json')
else:
return redirect('person_details',**{"slug":slug,"ptype":person.type})
And in the article view we just do:
response['comment_form'] = CommentForm()
And yes, I do not validate the comments form. There is no reason. Just one text input.
I have just started learning django. I created a form from django models. On the click of submit button the data is getting stored in database. Now what i want is something like the one given below :
#view.py
def contact(request):
if request.method == 'POST':
form = UserForm(request.POST)
if form.is_valid():
user = form.save()
return HttpResponseRedirect("/contact/create_db")
#urls.py
(r'^contact/$', views.contact),
(r'^contact/create_db$', views.do_create),
Now when i define do_create function in views.py i want to pass the arguments(user data of user form) like this:
def do_create(request, password, dbname, admin_password, confirm_password, demo_data=False, language=None, **kw):
Is this possible using django. How can this be achieved.
All you're asking here is how to get the value of the saved user in a subsequent view.
Well, this is easy. Once the user is saved, it (like any model instance) gets a pk value. You can use this in the URL for the subsequent view.
url(r'^contact/create_db/(?P<user_id>\d+)/$', views.do_create, 'do_create'),
In contact:
from django.shortcuts import redirect
...
user = form.save()
return redirect('do_create', kwargs={'user_id': user.pk})
And in do_create:
def do_create(request, user_id):
user = User.objects.get(pk=user_id)
Note the way I've passed in the URL name and arguments into redirect, rather than hard-coding the URL.
You don't need to pass them into a view. You want to pass them into a form. And you are already doing this:
form = UserForm(request.POST)
You pass your POST data into the form. If you want to do something with this data, use form.cleaned_data dictionary after form validation. Reading docs is also a good idea.
What approach is the best way to make content-types restricted to a user in Django?
Let us say I want all users with the user-role "blogger" to have its own blog.
I have created a weblog app. How do I restrict it so that the user logged in can only post in his "own" blog, and how do I make views that shows only a user's blog?
First your blog entries has to be attached to user, so you know on whos blog display, it, right? models.py:
class BlogEntry(models.Model):
user = models.ForeignKey(User, related_name='blog_entries')
other_field_1 = ...
other_field_2 = ...
Next, skip it in ModelForm, forms.py:
class BlogEntryModelForm(forms.ModelForm):
class Meta:
exclude = ('user',)
Then, when user want to post entry you require he's logged, views.py:
#login_required
def post_blog_entry(request):
....
if request.method == 'POST':
form = BlogEntryModelForm(request.POST)
if form.is_valid():
new_entry = form.save(commit=False)
new_entry.user = request.user
new_entry.save()
When you want display some user blog, views.py:
def view_blog(request, blogger_name):
user = get_object_or_404(User, username=blogger_name)
entries = user.blog_entries.all()
User is django.contrib.auth.models.User
You can add custom role checking to views above to display 404 page or error page if user has no rights to create blog.
Optionally you can replace User from django.contrib.auth with your own User implementation but you'll have to write model, authentication and middleware for it as well...
I didnt try to implement this, but I found another soultion that worked very good. It was easy to implement and did everything i wanted.
Check it out...