Make a Django model read-only? - django

What it says on the tin. Is there a way to make a Django model read-only?
By this I mean a Django model in which once records have been created, they can't be edited.
This would be useful for a model that records transaction history.

You can override the model's save method and check whether it's an existing entity, in which case you won't save any changes:
def save(self, *args, **kwargs):
if self.id is None:
super(ModelName, self).save(*args, **kwargs)
So in this example you only save the changes when the entity has not got an id yet, which is only the case when it's a new entity that hasn't been inserted yet.

You can override the save method and not call super if you wanted to. That'd be a fairly easy way of accomplishing this.
# blatantly ripped the save from another answer, since I forgot to save original model
def save(self, *args, **kwargs):
if self.id is None:
super(ModelName, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
return
You should probably also raise an exception if a delete or update is attempting to occur instead of simply returning. You want to signal the user what is happening - that the behaviour isn't valid.

In addition to other solutions: If your main goal is to avoid write access from the admin, you can modify the used admin class so that nobody has an add/change permission:
class HistoryAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False

If you don't want an attempt to modify a record to fail silently:
def save(self, *args, **kwargs):
if self.pk:
(raise an exception)
super(YourModel, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
(raise an exception)

Related

Django Generic views not working for PUT , GET , UPDATE , PATCH

I have RetrieveUpdateDestroyAPIView view like this.
class TaskRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView):
lookup_field = 'id'
serializer_class = TasksSerializer
def get_queryset(self):
query_set=Task.objects.get(id=self.kwargs['id'])
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
and my urls like this
path('task_detail/<int:id>', TaskRetrieveUpdateDestroyAPIView.as_view(), name="get_task"),
I am trying to PUT , PATCH , GET but getting same error
{
"detail": "Not found.",
"status_code": 404
}
The issue is in the function get_queryset, It expects a queryset but yours returns a single object, that's what the get function does as described here. So, you need to either set the queryset class field or use the get_queryset function.
You don't need to look up the task object yourself, that's what the generic view does for you. also you don't have to specify the method handlers(get, post, i.e.) yourself, they are already generated because you use RetrieveUpdateDestroyAPIView class. Also, since the lookup field defaults to the primary key(id), so, you could omit that, too
Try this code
class TaskRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView):
queryset = Task.objects.all()
serializer_class = TasksSerializer
and use pk instead of id
path('task_detail/<int:pk>', TaskRetrieveUpdateDestroyAPIView.as_view(), name="get_task")
or you could leave the lookup field as id and use it in the path function. It's pretty much the same thing, just saving some code

How can I add if statement to a deleteview? CBV django

Hey I want to add if statement and according to it decide if to delete the object or not.
I could not find it online.
In general how can I add if statements to any CBV including Update for example..
This is my DeleteView func:
class PostDeleteView(LoginRequiredMixin, DeleteView):
model = Post
success_url = reverse_lazy('TheApp:post_list')
EDIT! THE SOLUTION THAT WORKED FOR ME:(Thanks to AKX)
def delete(self, request, *args, **kwargs):
if (Post.author == request.user.username):
return super().delete(request, *args, **kwargs)
else:
return HttpResponse('You are not the owner of this Post! You can not delete it!')
Well, as you know, CBVs' methods map to HTTP methods, so just override delete() and add your condition:
class SomeView(..., DeleteView, ...):
def delete(self, request, *args, **kwargs):
if request.GET.get('really') != 'true':
return HttpResponse('I knew you were just kidding!')
return super().delete(request, *args, **kwargs)

Django DetailView additional checks based on object

Which method should be overridden to add additional checks and redirect accordingly?
i.e. I've a DetailView for my product page, and if this product is not published (and brand has more products) I want to redirect to the brand page.
I added this check to get method and I'm calling get_object() manually and then doing my checks, but in the end I'm also calling the super().get() which calls get_object() as well, this makes the SQL run twice.
The solution I've found is overriding the get_object() method as following..
def get_object(self, queryset=None):
if not hasattr(self, 'object') or not self.object:
self.object = super().get_object(queryset=queryset)
return self.object
This doesn't feel right though, what is the best way to do checks without triggering get_object twice?
My code that calls get_object twice looks like this: without the hack above.
def get(self, request, *args, **kwargs):
product = self.get_object()
if not product.published:
if product.brand and #more products from brand exists#
return redirect(reverse('brand',
args=(product.brand.slug,)))
else:
return redirect(reverse('pages:home'))
return super().get(request, *args, **kwargs)
just for reference super().get looks like this, and I don't want to rewrite these lines.
https://ccbv.co.uk/projects/Django/1.10/django.views.generic.detail/DetailView/#get
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
I think this is cleaner - store the response of the super().get(...) call, which will also populate self.object, then redirect if necessary, or return the response stored from the super().get(...) call:
def get(self, request, *args, **kwargs):
super_response = super().get(request, *args, **kwargs)
if not self.object.published:
if self.object.brand and #more products from brand exists#
return redirect(reverse('brand',
args=(self.object.brand.slug,)))
else:
return redirect(reverse('pages:home'))
return super_response
Note that this does have the overhead of creating a valid response for an unpublished object. To avoid that, simply avoid the super call - yes, this means duplicating two lines of code from the superclass' method:
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if not self.object.published:
if self.object.brand and #more products from brand exists#
return redirect(reverse('brand',
args=(self.object.brand.slug,)))
else:
return redirect(reverse('pages:home'))
context = self.get_context_data(object=self.object)
return self.render_to_response(context)

Django mixins for class-based-generic views

I am trying to implement staff_member_required mixins:
Here are the two ways I found on how to do so:
First:
class StaffRequiredMixin(object):
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
if not request.user.is_staff:
messages.error(
request,
'You do not have the permission required to perform the '
'requested operation.')
return redirect(settings.LOGIN_URL)
return super(StaffRequiredMixin, self).dispatch(request,
*args, **kwargs)
Second:
class StaffRequiredMixin(object):
#classmethod
def as_view(self, *args, **kwargs):
view = super(StaffRequiredMixin, self).as_view(*args, **kwargs)
return staff_member_required(view)
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
if not request.user.is_staff:
messages.error(
request,
'You do not have the permission required to perform the '
'requested operation.')
return redirect(settings.LOGIN_URL)
return super(StaffRequiredMixin, self).dispatch(request,
*args, **kwargs)
What I want to know is:
Why the second way is overriding the as_view() method and wrapping it with staff_member_required ?
Do we get any 'additional' advantages by doing so ?
I am new to these mixins. Please help.
TL; DR: they're close to the same, the difference is in checking is_active as well as is_staff and the error messages. You probably don't need both because the as_view override negates the need for the dispatch override anyway.
These are really just two ways of doing close to the same thing.
This code:
class StaffRequiredMixin(object):
#classmethod
def as_view(self, *args, **kwargs):
view = super(StaffRequiredMixin, self).as_view(*args, **kwargs)
return staff_member_required(view)
...could actually be used alone to implement the staff_member_required decorator. In this case the staff_member_required functionality gets called in the view's as_view() function (i.e., from as_view() in your URLConf).
This code:
class StaffRequiredMixin(object):
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
if not request.user.is_staff:
messages.error(
request,
'You do not have the permission required to perform the '
'requested operation.')
return redirect(settings.LOGIN_URL)
return super(StaffRequiredMixin, self).dispatch(request,
*args, **kwargs)
...filters users in the dispatch method. You can see in the Django codebase that as_view actually calls dispatch. This means that if you use both together you won't actually ever trigger the if not request.user.is_staff code in the dispatch method because any user who doesn't pass would have been filtered out in the as_view method.
The second difference is that staff_member_required is slightly different from what the first code does. If you check out the code, you'll notice that staff_member_required also checks whether the user's is_active flag passes (not just is_staff like in your dispatch decorator). It also doesn't pass the messages.error like in your code.

Django custom forms for Admin deleting fields dynamically

The problem is I have a model called Gift. And it has a boolean field 'giftbought' that I want to hide in admin interface when the object is being created and show it when it is being updated.
I tryed making a form, overriding init method like:
class GiftForm(forms.ModelForm):
giftbought = forms.BooleanField(label=u"Bought?", required=False)
class Meta:
model = Gift
def __init__(self, *args, **kwargs):
super(GiftForm, self).__init__(*args, **kwargs)
if not self.instance.pk:
del self.fields['giftbought']
But it doesn't work for admin, like it is being said in:
Remove fields from ModelForm
I thing I needed to make a class ModelAdmin, overriding get_form method, but I don't know how to check if I is_instance or not...
It would be something like:
class GiftAdmin(admin.ModelAdmin):
model = Gift
def get_form(self, request, obj=None, **kwargs):
# that IF doesnt work!!!
if not self.instance.pk:
self.exclude = ('giftbought',)
return super(GiftAdmin, self).get_form(request, obj=None, **kwargs)
admin.site.register(Gift, GiftAdmin)
Any hint?
Definitely your best bet is ModelAdmin. I think you got it right except for the if test.
You should be able to do it like this:
class GiftAdmin(admin.ModelAdmin):
model = Gift
def get_form(self, request, obj=None, **kwargs):
self.exclude = []
# self.instance.pk should be None if the object hasn't yet been persisted
if obj is None:
self.exclude.append('giftbought')
return super(GiftAdmin, self).get_form(request, obj, **kwargs)
admin.site.register(Gift, GiftAdmin)
Notice there are minor changes to the method code. You should check the docs, I'm sure you'll find everything you need there.
Hope this helps!