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

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).

Related

Test a Django model that uses request.user in save_model

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.

Django session variables lost when set in form_valid() of class-based views

Using Django, if I set a session variable within the post() method of a django.views.generic.edit.FormView class, that variable is then available for future requests.
e.g.
def post(self, request, *args, **kwargs):
"""
Store useful data in session variable for future requests
"""
if 'useful_data' in request.POST:
request.session['useful_data'] = useful_data
return HttpResponseRedirect(self.get_success_url())
If however, I attempt to set a session variable via the form_valid() method of a django.views.generic.edit.FormView class, the changes I make to the variable seem to disappear before the next request.
e.g.
def form_valid(self, form):
"""
Store useful data in session variable for future requests
"""
# Useful data that I only want to update if the form was validated
self.request.session['useful_data'].update(form.cleaned_data['useful_data'])
return HttpResponseRedirect(self.get_success_url())
So, how can I make persistent changes to session variables from the form_valid() method of a django.views.generic.edit.FormView class?
I suspect the problem could be the update part:
self.request.session['useful_data'].update(form.cleaned_data['useful_data'])
If we take a look at the documentation, the issue could be django does not know that the session has been modified and thus does not change it.
To validate this, make it explicit that session has been modified:
self.request.session['useful_data'].update(form.cleaned_data['useful_data'])
self.request.session.modified = True
Or just assign the value without using update(), just like in the documentation:
self.request.session['useful_data'] = form.cleaned_data['useful_data']
Hope it helps!

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 REST Framework: creating hierarchical objects using URL arguments

I have a django-rest-framework REST API with hierarchical resources. I want to be able to create subobjects by POSTing to /v1/objects/<pk>/subobjects/ and have it automatically set the foreign key on the new subobject to the pk kwarg from the URL without having to put it in the payload. Currently, the serializer is causing a 400 error, because it expects the object foreign key to be in the payload, but it shouldn't be considered optional either. The URL of the subobjects is /v1/subobjects/<pk>/ (since the key of the parent isn't necessary to identify it), so it is still required if I want to PUT an existing resource.
Should I just make it so that you POST to /v1/subobjects/ with the parent in the payload to add subobjects, or is there a clean way to pass the pk kwarg from the URL to the serializer? I'm using HyperlinkedModelSerializer and ModelViewSet as my respective base classes. Is there some recommended way of doing this? So far the only idea I had was to completely re-implement the ViewSets and make a custom Serializer class whose get_default_fields() comes from a dictionary that is passed in from the ViewSet, populated by its kwargs. This seems quite involved for something that I would have thought is completely run-of-the-mill, so I can't help but think I'm missing something. Every REST API I've ever seen that has writable endpoints has this kind of URL-based argument inference, so the fact that django-rest-framework doesn't seem to be able to do it at all seems strange.
Make the parent object serializer field read_only. It's not optional but it's not coming from the request data either. Instead you pull the pk/slug from the URL in pre_save()...
# Assuming list and detail URLs like:
# /v1/objects/<parent_pk>/subobjects/
# /v1/objects/<parent_pk>/subobjects/<pk>/
def pre_save(self, obj):
parent = models.MainObject.objects.get(pk=self.kwargs['parent_pk'])
obj.parent = parent
Here's what I've done to solve it, although it would be nice if there was a more general way to do it, since it's such a common URL pattern. First I created a mixin for my ViewSets that redefined the create method:
class CreatePartialModelMixin(object):
def initial_instance(self, request):
return None
def create(self, request, *args, **kwargs):
instance = self.initial_instance(request)
serializer = self.get_serializer(
instance=instance, data=request.DATA, files=request.FILES,
partial=True)
if serializer.is_valid():
self.pre_save(serializer.object)
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Mostly it is copied and pasted from CreateModelMixin, but it defines an initial_instance method that we can override in subclasses to provide a starting point for the serializer, which is set up to do a partial deserialization. Then I can do, for example,
class SubObjectViewSet(CreatePartialModelMixin, viewsets.ModelViewSet):
# ....
def initial_instance(self, request):
instance = models.SubObject(owner=request.user)
if 'pk' in self.kwargs:
parent = models.MainObject.objects.get(pk=self.kwargs['pk'])
instance.parent = parent
return instance
(I realize I don't actually need to do a .get on the pk to associate it on the model, but in my case I'm exposing the slug rather than the primary key in the public API)
If you're using ModelSerializer (which is implemented by HyperlinkedModelSerializer) it's as easy as implementing the restore_object() method:
class MySerializer(serializers.ModelSerializer):
def restore_object(self, attrs, instance=None):
if instance is None:
# If `instance` is `None`, it means we're creating
# a new object, so we set the `parent_id` field.
attrs['parent_id'] = self.context['view'].kwargs['parent_pk']
return super(MySerializer, self).restore_object(attrs, instance)
# ...
restore_object() is used to deserialize a dictionary of attributes into an object instance. ModelSerializer implements this method and creates/updates the instance for the model you specified in the Meta class. If the given instance is None it means the object still has to be created, so you just add the parent_id attribute on the attrs argument and call super().
So this way you don't have to specify a read-only field, or have a custom view/serializer.
More information:
http://www.django-rest-framework.org/api-guide/serializers#declaring-serializers
Maybe a bit late, but i guess this drf nested routers library could be helpful for that operation.

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