Test a Django model that uses request.user in save_model - python

I have a Django model that overrides save_model in order to automatically save the current authenticated user into a field on save.
The question is, how in a unit test can I test the creation of this model, without having to go through a view?
The model:
class SomeClass(models.Model):
def save_model(self, request, obj, form, change):
self.prevent_update()
if not obj.pk:
# Only set added_by during the first save.
obj.created = request.user
super().save_model(request, obj, form, change)
The test just wants to set up SomeClass before doing some other stuff.
class SomeClassTestCase(TestCase):
def setUp(self):
self.assertTrue(request.user)
SomeClass.objects.create(name="abc")
I know that there is a request getting set up automatically, since request.user doesn't fail with request being None; instead, the query constraint fails because the user is null. How do I set a user on the request that appears to have been passed into save_model
Alternatively, I know I can setup a request using RequestFactory and generate a user. In that case, how do I get that into the test so that the SomeClass.objects.create actually sees it.

Thanks to the comments, I realised that I had copied and pasted badly, placing save_model into a model. This and my amateurish abilities with Django caused all kinds of confusion. So the fact that request.user was not failing was because it was never called.
I am now correctly overriding the save method in the model, and the save_model method in the admin, which passes the correct details to the model for it to save.

Related

Django: DRY principle and UserPassesTestMixin

I have a model named Post and have a field there called owner (foreign key to User). Of course, only owners can update or delete their own posts.
That being said, I use login_required decorator in the views to make sure the user is logged in but then, I also need to make sure the user trying to update/delete the question is the owner.
As I'm using Django: Generic Editing Views the documentation says I need to use Django: UserPassesTestMixin.
This validation will be done for the update and delete views. DRY, what is the way to go about this? should I create a class named TestUserOwnerOfPost and create a test_func() and then make the update and delete views inherit from it?
Cause that's what I have tried and didn't work, code below:
from django.views.generic.edit import UpdateView
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import UserPassesTestMixin
class TestUserOwnerOfPost(UserPassesTestMixin):
def test_func(self):
return self.request.user == self.post.owner
class EditPost(UpdateView, TestUserOwnerOfPost):
model = Post
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(EditPost, self).dispatch(*args, **kwargs)
With the code above, every logged-in user in the system can edit/delete any post. What am I doing wrong? am I missing something? thanks.
The first problem is that the order of the classes you inherit is incorrect, as #rafalmp says.
However, fixing that doesn't solve the problem, because the UserPassesTest mixin performs the test before running the view. This means that it's not really suitable to check the owner of self.object, because self.object has not been set yet. Note I'm using self.object instead of self.post -- I'm don't think that the view ever sets self.post but I might be wrong about that.
One option is to call self.get_object() inside the test function. This is a bit inefficient because your view will fetch the object twice, but in practice it probably doesn't matter.
def test_func(self):
self.object = self.get_object()
return self.request.user == self.object.owner
Another approach is to override get_queryset, to restrict it to objects owned by the user. This means the user will get a 404 error if they do not own the object. This behaviour is not exactly the same as the UserPassesTestMixin, which will redirect to a login page, but it might be ok for you.
class OwnerQuerysetMixin(object):
def get_queryset(self):
queryset = super(OwnerQuerysetMixin, self).get_queryset()
# perhaps handle the case where user is not authenticated
queryset = queryset.filter(owner=self.request.user)
return queryset
The order of the classes you inherit from matters. For your access control to work, it must be enforced before UpdateView is executed:
class EditPost(TestUserOwnerOfPost, UpdateView):

How to implement a script while saving a file in django admin panel?

I'm new to django.
I want to run a script(for ex. zipping a file) after it gets uploaded to a server through "admin panel",i.e when user hits Save "in" from admin panel,it should get zipped(or some other manipulation that i may want to implement) after it gets uploaded.
Or can you just tell me which function is called when user hits the save button.
Signals might work, but it seems like the OP wants to do something only when an object is created or changed from the admin panel.
I think the best way to do this is to use the ModelAdmin method save_model().
From the Django docs:
ModelAdmin.save_model(self, request, obj, form, change)
You can overwrite this method in your definition of an admin class, as follows:
class SomeObjectAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# do any pre-save stuff here
obj.save()
The change arg is a Boolean value that is True if the object is being changed, and false if the object is being created for the first time. So if you want to execute some function only on object creation:
def save_model(self, request, obj, form, change):
if not change:
# do your compression here
# do any other pre-save stuff here
obj.save()
# do any post-save stuff here
You may use signals : https://docs.djangoproject.com/en/dev/topics/signals/
to detect the save action.

Django, class-views: How can I save session data with a form's object?

I'm trying to store the username from the current request's session into a db object. How can I do this from within a class-based view? Is there a "clean" way to do this? What should I override/subclass?
I have a model that looks like this:
from django.contrib.auth.models import User
class Entry(django.db.models.Model):
...
author = models.ForeignKey(User, editable=False)
I also have a view based on the built-in generic view django.views.generic.CreateView. I'm also using the default ModelForm class that goes with my model, and the default {{ form }} in my template. AFAIK, the session and authentication apps/middleware are set up properly---as per default in new Django projects.
I found this post, which is getting at about the same thing, but from the wrong angle, and using function views instead.
My thinking so far was to override something in the form class and insert the username into the cleaned data. Is there a better way? Is there a right way?
Edit: Solution so far, non-working, with an IntegrityError: author_id cannot be null
from django.views.generic import CreateView
class Index(CreateView):
model = magicModel
template_name = "index.html"
success_url = "/magicWorked"
...
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.author = request.user
return super(Index, self).form_valid(form)
I wrote this based on what I found in django/views/generic/edit.py, which uses this implementation for class ModelFormMixin:
def form_valid(self, form):
self.object = form.save()
return super(ModelFormMixin, self).form_valid(form)
This is the method called by super().form_valid() above.
Edit: The problem with my solution was my understanding of Python's inheritance model. When the super-class calls form_valid(), it calls its own version, not my override; my code was never running at all.
The "correct" way to do this is to write your own view for object creation if the generic view doesn't suffice. Creation views are relatively short and there are numerous examples of how to save foreign keys.
Incidentally, Django's 1.3 docs say somewhere in there that modifications to the authentication model used by the admin app are being "discussed," such as adding per-instance permissions. (The current auth model supports only per model permissions.) The dev's might also add an implementation for what I'm trying to achieve. After all, user-associated data is used by nearly all websites.

How to access REMOTE_ADDR in a model's save() method

I have a model (UserProfile) with which I want to record a user's IP address when new user accounts are created.
I've begin the process of overriding the Django model's save() method, but I am totally uncertain how to properly access HttpRequest attributes from within a model. Can any of you guys help? Google was unable to provide a specific answer for me.
You can get to the request from within the admin, but you can't do it in general code. If you need it then you'll have to assign it manually in the view.
You always need to pass on request information from the view to your save-method.
Consider that saving an instance doesn't always have to be invoked from a http request (for a simple example think of calling save in the python shell).
If you need to access the request object within the admin while saving, you can override it's save_model method:
def save_model(self, request, obj, form, change):
# do something with obj and request here....
obj.save()
But otherwise you always have to pass it on from the view:
def my_view(request):
obj = MyClass(ip_address = request.META['REMOTE_ADDR'])
Or to make this easier to re-use, make a method on the model like:
def foo(self, request):
self.ip_address = request.META['REMOTE_ADDR']
self..... = request.....
and call it from your view with obj.foo(request).

How to make a model instance read-only after saving it once?

One of the functionalities in a Django project I am writing is sending a newsletter. I have a model, Newsletter and a function, send_newsletter, which I have registered to listen to Newsletter's post_save signal. When the newsletter object is saved via the admin interface, send_newsletter checks if created is True, and if yes it actually sends the mail.
However, it doesn't make much sense to edit a newsletter that has already been sent, for the obvious reasons. Is there a way of making the Newsletter object read-only once it has been saved?
Edit:
I know I can override the save method of the object to raise an error or do nothin if the object existed. However, I don't see the point of doing that. As for the former, I don't know where to catch that error and how to communicate the user the fact that the object wasn't saved. As for the latter, giving the user false feedback (the admin interface saying that the save succeded) doesn't seem like a Good Thing.
What I really want is allow the user to use the Admin interface to write the newsletter and send it, and then browse the newsletters that have already been sent. I would like the admin interface to show the data for sent newsletters in an non-editable input box, without the "Save" button. Alternatively I would like the "Save" button to be inactive.
You can check if it is creation or update in the model's save method:
def save(self, *args, **kwargs):
if self.pk:
raise StandardError('Can\'t modify bla bla bla.')
super(Payment, self).save(*args, **kwargs)
Code above will raise an exception if you try to save an existing object. Objects not previously persisted don't have their primary keys set.
Suggested reading: The Zen of Admin in chapter 17 of the Django Book.
Summary: The admin is not designed for what you're trying to do :(
However, the 1.0 version of the book covers only Django 0.96, and good things have happened since.
In Django 1.0, the admin site is more customizable. Since I haven't customized admin myself, I'll have to guess based on the docs, but I'd say overriding the model form is your best bet.
use readonlyadmin in ur amdin.py.List all the fields which u want to make readonly.After creating the object u canot edit them then
use the link
http://www.djangosnippets.org/snippets/937/
copy the file and then import in ur admin.py and used it
What you can easily do, is making all fields readonly:
class MyModelAdmin(ModelAdmin):
form = ...
def get_readonly_fields(self, request, obj=None):
if obj:
return MyModelAdmin.form.Meta.fields
else: # This is an addition
return []
As for making the Save disappear, it would be much easier if
has_change_permission returning False wouldnt disable even displaying the form
the code snippet responsible for rendering the admin form controls (the admin_modify.submit_row templatetag), wouldnt use show_save=True unconditionally.
Anyways, one way of making that guy not to be rendered :
Create an alternate version of has_change_permission, with proper logic:
class NoSaveModelAdminMixin(object):
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
response = super(NoSaveModelAdmin, self).render_change_form(request, context, add, change,form_url, obj)
response.context_data["has_change_permission"] = self.has_real_change_permission(request, obj)
def has_real_change_permission(self, request, obj):
return obj==None
def change_view(self, request, object_id, extra_context=None):
obj = self.get_object(request, unquote(object_id))
if not self.has_real_change_permission(request, obj) and request.method == 'POST':
raise PermissionDenied
return super(NoSaveModelAdmin, self).change_view(request, object_id, extra_context=extra_context)
Override the submit_row templatetag similar to this:
#admin_modify.register.inclusion_tag('admin/submit_line.html', takes_context=True)
def submit_row(context):
...
'show_save': context['has_change_permission']
...
admin_modify.submit_row = submit_row

Categories

Resources