Bit of a strange one and wondering if anyone else here has come across this.
I have a standard DeleteView with the GET showing a confirmation page containing a form that posts to the delete view.
Whenever I click confirm nothing happens - the post to the view occurs and it redirects as intended, however the object is not deleted.
If I then perform the action a second time the object is deleted.
class MetricDeleteView(DeleteView):
template_name = "dashboard/administration/metric/delete.html"
button_title = "Update metric"
form_class = MetricUpdateForm
model = dashboard_metric
#cached_property
def dashboard_score(self):
return self.get_object().score
def get_success_url(self):
return reverse_lazy("administration:dashboard:update_score", kwargs={
'dashboard': self.dashboard_score.dashboard.id,
'pk': self.dashboard_score.id
})
I can't for the life of me figure out why this is occurring across all some models on my site.
Hm, interesting. As the view is generic, have you looked at the model to check it doesn't override the delete functionality? Perhaps it doesn't delete on the first pass and sets a variable to 'deleted' instead, especially if you're working with syncing across platforms. WatermelonDB, for instance. Nathan. :D
Related
I have a model which creates Memo objects. I would like to use a custom Model Manager's posted method to return the total number of Memo objects - then use this number within a template. I am trying to keep as much of my code as possible within my Models and Model Managers and less within my Views as I read that this was a best practice in 'Two Scoops of Django'.
In the shell I can get the number of memos as such:
>>> from memos.models import Memo
>>> Memo.objects.all()
<QuerySet [<Memo: Test Memo 2>, <Memo: Test Memo 1>]>
>>> Memo.objects.all().count()
2
This is what my Model and Model Manager look like:
class MemoManager(models.Manager):
use_for_related_fields = True
def posted(self):
return self.count()
class Memo(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_time = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
objects = MemoManager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('memos-detail', kwargs={'pk': self.pk})
I know this is clearly the wrong way to do it but I have confused myself here. So how do I use my Model Manager to get the count of objects and use it in a template like: {{ objects.all.count }}?
P.S. I see other posts that show how to do this within the view but as stated I am trying not to use the view. Is using the view required? I also understand my posted method is written incorrectly.
I'm sorry but you have misinterpreted what was written in TSD. The Lean View Fat Model is meant to keep code which pertains to 'business logic' out of the views, and certain model specific things. A request should be handled by a view. So when you want to load a template, you must first have a GET request to your app.
A view function should be written such that Validation of POST data or the Creation of a new object in DB or Querying/Filtering for GET requests should be handled in the corresponding serializer/model/model manager.
What should be happening while you want to load your template.
Have a url for the template that you have created and a view function mapped for it
In the view function you should render said template and pass the necessary data inside the context.
To keep in line with the Lean View Fat Model style, if you want to get a Queryset of of Memo's but only those which have their is_deleted fields set to False, you can overwrite the model manager get_queryset() method for Memo model.
If you want to create a new Memo with a POST request, you can handle
the creation using a ModelForm!
Hope this clears things up!
EDIT:
How to pass a context to a template, in your case the memo count.
def random_memo_view(request):
context = {'memo_count': Memo.posted()}
return render(request, 'template.html', context=context)
RE-EDIT
I just checked that you were using DetailView. In this case follow this from the django docs.
Class Based Views: Adding Extra Context
i am really new to Django, but since i am doing a fairly easy app i was suggested by a friend to use only the admin site which is fast and easy, i am almost done with my project, but i need one thing i don't seem to find anywhere.
My model is about adding programming problems, however i need to have a field that identifies which user added it and that only that user and the super users can erase or change the problem he just added.
Of course adding the field is quite simple, however how do i recognize who is adding the problem? and how to validate only him and the super users can change or delete that said problem?. I believe this is the most challenging phase of this project, can you help me?.
Thanks in advance :)!
EDIT: this is what i tried after the answer i just got recently, but i am quite stuck :/
EDIT2: this is now how it looks, but it gives my a type error and brings down the whole adminsite with this :
"has_change_permission() takes exactly 3 arguments (2 given)"
EDIT3: now i have changed the code but it still let regular staff users to erase delete entries, and sadly now it gives the error in EDIT 2 when i try to modify the content of the entry.
EDIT4: Finally thanks to your help i could make it impossible for a common user to delete directly on the database, however the default delete by queryset still works for them, what can i do?
EDIT5: Thanks for all the help you gave me today, now it works and it is wonderful! i leave the code so someone else with this same issue can just do it, i had to do such a long research to get rid of the "delete_element" from django's admin the one that is setted as default.
from django.contrib import admin
from .models import Problemas
# Register your models here.
from django.contrib.admin.actions import delete_selected as delete_selected_
def delete_selected(modeladmin,request,queryset):
for obj in queryset:
if (obj.User==request.user.username or request.user.is_superuser):
obj.delete()
class ProblemasAdmin(admin.ModelAdmin):
list_display = ('Juez', 'Nombre', 'Categoria','Dificultad','URL','User')
list_filter = ('Juez','Categoria','Dificultad','User')
search_fields = ['Nombre']
readonly_fields = ('User',)
list_per_page = 20
actions = [delete_selected]
def save_model(self, request, obj, form, change):
if (obj.User==""):
obj.User = request.user.username
obj.save()
def delete_model(self,request,obj):
for o in obj.all():
if (o.User==request.user.username or request.user.is_superuser):
o.delete()
def has_change_permission(self,request,obj=None):
return obj==None or request.user.username == obj.User or request.user.is_superuser
def has_delete_permission(self,request,obj=None):
return obj==None or request.user.username == obj.User or request.user.is_superuser
admin.site.register(Problemas,ProblemasAdmin)
#register(QuestionModel)
class QuestionModelAdmin(ModelAdmin):
def has_change_permission(self,request,obj=None):
return request.user == obj.owner
def has_delete_permission(self,request,obj=None):
return request.user == obj.owner
I think should work
I'm new to the web development world, to Django, and to applications that require securing the URL from users that change the foo/bar/pk to access other user data.
Is there a way to prevent this? Or is there a built-in way to prevent this from happening in Django?
E.g.:
foo/bar/22 can be changed to foo/bar/14 and exposes past users data.
I have read the answers to several questions about this topic and I have had little luck in an answer that can clearly and coherently explain this and the approach to prevent this. I don't know a ton about this so I don't know how to word this question to investigate it properly. Please explain this to me like I'm 5.
There are a few ways you can achieve this:
If you have the concept of login, just restrict the URL to:
/foo/bar/
and in the code, user=request.user and display data only for the logged in user.
Another way would be:
/foo/bar/{{request.user.id}}/
and in the view:
def myview(request, id):
if id != request.user.id:
HttpResponseForbidden('You cannot view what is not yours') #Or however you want to handle this
You could even write a middleware that would redirect the user to their page /foo/bar/userid - or to the login page if not logged in.
I'd recommend using django-guardian if you'd like to control per-object access. Here's how it would look after configuring the settings and installing it (this is from django-guardian's docs):
>>> from django.contrib.auth.models import User
>>> boss = User.objects.create(username='Big Boss')
>>> joe = User.objects.create(username='joe')
>>> task = Task.objects.create(summary='Some job', content='', reported_by=boss)
>>> joe.has_perm('view_task', task)
False
If you'd prefer not to use an external library, there's also ways to do it in Django's views.
Here's how that might look:
from django.http import HttpResponseForbidden
from .models import Bar
def view_bar(request, pk):
bar = Bar.objects.get(pk=pk)
if not bar.user == request.user:
return HttpResponseForbidden("You can't view this Bar.")
# The rest of the view goes here...
Just check that the object retrieved by the primary key belongs to the requesting user. In the view this would be
if some_object.user == request.user:
...
This requires that the model representing the object has a reference to the User model.
In my project, for several models/tables, a user should only be able to see data that he/she entered, and not data that other users entered. For these models/tables, there is a user column.
In the list view, that is easy enough to implement, just filter the query set passed to the list view for model.user = loggged_id.user.
But for the detail/update/delete views, seeing the PK up there in the URL, it is conceivable that user could edit the PK in the URL and access another user's row/data.
I'm using Django's built in class based views.
The views with PK in the URL already have the LoginRequiredMixin, but that does not stop a user from changing the PK in the URL.
My solution: "Does Logged In User Own This Row Mixin"
(DoesLoggedInUserOwnThisRowMixin) -- override the get_object method and test there.
from django.core.exceptions import PermissionDenied
class DoesLoggedInUserOwnThisRowMixin(object):
def get_object(self):
'''only allow owner (or superuser) to access the table row'''
obj = super(DoesLoggedInUserOwnThisRowMixin, self).get_object()
if self.request.user.is_superuser:
pass
elif obj.iUser != self.request.user:
raise PermissionDenied(
"Permission Denied -- that's not your record!")
return obj
Voila!
Just put the mixin on the view class definition line after LoginRequiredMixin, and with a 403.html template that outputs the message, you are good to go.
In django, the currently logged in user is available in your views as the property user of the request object.
The idea is to filter your models by the logged in user first, and then if there are any results only show those results.
If the user is trying to access an object that doesn't belong to them, don't show the object.
One way to take care of all of that is to use the get_object_or_404 shortcut function, which will raise a 404 error if an object that matches the given parameters is not found.
Using this, we can just pass the primary key and the current logged in user to this method, if it returns an object, that means the primary key belongs to this user, otherwise it will return a 404 as if the page doesn't exist.
Its quite simple to plug it into your view:
from django.shortcuts import get_object_or_404, render
from .models import YourModel
def some_view(request, pk=None):
obj = get_object_or_404(YourModel, pk=pk, user=request.user)
return render(request, 'details.html', {'object': obj})
Now, if the user tries to access a link with a pk that doesn't belong to them, a 404 is raised.
You're going to want to look into user authentication and authorization, which are both supplied by [Django's Auth package] (https://docs.djangoproject.com/en/4.0/topics/auth/) . There's a big difference between the two things, as well.
Authentication is making sure someone is who they say they are. Think, logging in. You get someone to entire their user name and password to prove they are the owner of the account.
Authorization is making sure that someone is able to access what they are trying to access. So, a normal user for instance, won't be able to just switch PK's.
Authorization is well documented in the link I provided above. I'd start there and run through some of the sample code. Hopefully that answers your question. If not, hopefully it provides you with enough information to come back and ask a more specific question.
This is a recurring question and also implies a serious security flaw. My contribution is this:
There are 2 basic aspects to take care of.
The first is the view:
a) Take care to add a decorator to the function-based view (such as #login_required) or a mixin to the class-based function (such as LoginRequiredMixin). I find the official Django documentation quite helpful on this (https://docs.djangoproject.com/en/4.0/topics/auth/default/).
b) When, in your view, you define the data to be retrieved or inserted (GET or POST methods), the data of the user must be filtered by the ID of that user. Something like this:
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=User.objects.filter(pk=self.request.user.id))
return super().get(request, *args, **kwargs)
The second aspect is the URL:
In the URL you should also limit the URL to the pk that was defined in the view. Something like this:
path('int:pk/blog-add/', AddBlogView.as_view(), name='blog-add'),
In my experience, this prevents that an user sees the data of another user, simply by changing a number in the URL.
Hope it helps.
In django CBV (class based views) you can prevent this by comparing the
user entered pk and the current logged in user:
Note: I tested it in django 4 and python 3.9.
from django.http import HttpResponseForbidden
class UserDetailView(LoginRequiredMixin, DetailView):
model = your_model
def dispatch(self, request, *args, **kwargs):
if kwargs.get('pk') != self.request.user.pk:
return HttpResponseForbidden(_('You do not have permission to view this page'))
return super().dispatch(request, *args, **kwargs)
I am trying to get my head around Class based views (I'm new to Django). I currently have a project that uses function based views. My 'create' view renders a form and successfully submits to the database. However, I need an edit/update function so the obvious option is to re-use the 'create' function I made but I'm struggling to work it out and adhere to the DRY principle.
Is using Class based views the right way to go?
Do they handle the creation of all the 'CRUD' views?
I'm currently working my way through the GoDjano tutorials on Class based views but its still not sinking in.
Any help/pointers would be, as usual, much appreciated.
As you can see in the source code, a CreateView and an UpdateView are very similar. The only difference is that a CreateView sets self.object to None, forcing the creation of a new object, while UpdateView sets it to the updated object.
Creating a UpdateOrCreateView would be as simple as subclassing UpdateView and overriding the get_object method to return None, should a new object be created.
class UpdateOrCreateView(UpdateView):
def get_object(self, queryset=None):
# or any other condition
if not self.kwargs.get('pk', None):
return None
return super(UpdateOrCreateView, self).get_object(queryset)
The GoDjango tutorials don't seem to be out of date (CBVs have barely changed since their introduction), but they do seem to be missing some of the essential views in their tutorials.
CBV is in my opinion never the solution. A dry FBV is (assuming you have created an imported a form RecordForm and a model Record, imported get_object_or_404 and redirect):
#render_to('sometemplate.html')
def update(request, pk=None):
if pk:
record = get_object_or_404(Record, pk=pk)
else:
record = None
if request.POST:
form = RecordForm(request.POST)
if form.is_valid():
form.save()
return redirect('somepage')
else:
// ....
elif record:
form = RecordForm(instance=record)
else:
form = RecordForm()
return { 'form': form, 'record': record }
I also integrate the messages framework to for example add an error message when form.is_valid() is False.
I use a render_to decorator but that's not necessary (but then you have to return the view results differently).
Basically what I'm trying to achieve is a multi-model django app where different models take advantage of the same views. For example I've got the models 'Car' 'Make' 'Model' etc and I want to build a single view to perform the same task for each, such as add, delete and edit, so I don't have to create a seperate view for add car, ass make etc. I've built a ModelForm and Model object for each and would want to create a blank object when adding and a pre-populated form object when editing (through the form instance arg), with objects being determined via url parameters.
Where I'm stuck is that I'm not sure what the best way to so this is. At the moment I'm using a load of if statements to return the desired object or form based on parameters I'm giving it, which get's a bit tricky when different forms need specifying and whether they need an instance or not. Although this seems to be far from the most efficient way of achieving this.
Django seems to have functions to cover most repetitive tasks, is there some magic I'm missing here?
edit - Here's an example of what I'm doing with the arguments I'm passing into the url:
def edit_object(request, object, id):
if(object==car):
form = carForm(instance = Car.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
What about using Class Based Views? Using CBVs is the best way in Django to make reusable code. For this example maybe it can be a little longer than function based views, but when the project grows up it makes the difference. Also remember "Explicit is better than implicit".
urls.py
# Edit
url(r'^car/edit/(?P<pk>\d+)/$', EditCar.as_view(), name='edit-car'),
url(r'^make/edit/(?P<pk>\d+)/$', EditMake.as_view(), name='edit-make'),
# Delete
url(r'^car/delete/(?P<pk>\d+)/$', DeleteCar.as_view(), name='delete-car'),
url(r'^make/delete/(?P<pk>\d+)/$', DeleteMake.as_view(), name='delete-make'),
views.py
class EditSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-edit.html'
class EditCar(EditSomethingMixin, UpdateView):
model = Car
form_class = CarForm
class EditMake(EditSomethingMixin, UpdateView):
model = Make
form_class = MakeForm
class DeleteSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-delete.html'
class DeleteCar(DeleteSomethingMixin, DeleteView):
model = Car
class DeleteMake(DeleteSomethingMixin, DeleteView):
model = Make
Just pass your class and form as args to the method then call them in the code.
def edit_object(request, model_cls, model_form, id):
form = model_form(instance = model_cls.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
then just pass in the correct classes and forms in your view methods
def edit_car(request,id):
return edit_object(request, Car, CarForm, id)
each method knows what classes to pass, so you eliminate the if statements.
urls.py
url(r'^car/delete/(?<pk>\d+)/', edit, {'model': Car})
url(r'^make/delete/(?<pk>\d+)/', edit, {'model': Make})
views.py
def edit(request, id, model):
model.objects.get(id=id).delete()