Don't overcome to call function inside another one - python

I'm working with django 1.11 and I would like to call a little function inside another one.
The issue comes from parameter inside the called function.
The function lets to get the email from the logged user :
def get_user_email(request):
user_email = request.user.email
return user_email
And the other one make some things, but I need to pick up the email :
#shared_task(bind=True, time_limit=3600, soft_time_limit=3600)
def get_xls_export(self, model="", search_info="", query_params=None, **kwargs):
# Some things
# Call the previous function
user_email = get_user_email()
Which attributes I have to write in my function in order to call it correctly ?
Thank you

Short answer: you can't do this.
Longer answer: in the (totally useless FWIW) get_user_email() function, request is supposed to be the current HTTPRequest instance, which is of course only available within a view. To use it in a celery task, you'd need to pass the request object from the view to the task, but that would be a bad idea too (it would couple your task code to a whole lot of things it doesn't need and has no business knowing about, it would make testing much more difficult, and it might even expose sensitive data), if that's even possible actually (not sure how django's HTTPRequest supports serialization).
The proper solution here is to force the caller to explicitely pass the informations the task needs, and only those informations. In your case, depending on what you use the user's email for and whether it's ok to have potentially stale data, you want to pass either the plain email, or pass the user's id and re-read the model from the task (this makes sure the email is up to date when the task is executed).
And really, this get_user_email function (if what you posted is the full implementation of course) is useless and does more harm than good wrt/ readability.

Related

What does this mean in the django documentation about class-based views?

A rookie here. As I am reading the django documentation, I came up with a note that I cannot fully understand.
It says:
Note
While your class is instantiated for each request dispatched to it, class attributes set through the as_view() entry point are configured only once at the time your URLs are imported.
Here is the link:
https://docs.djangoproject.com/en/2.2/topics/class-based-views/intro/
So which one is better? What advantage does each have? I've tried both and cannot experience any difference(Pretty sure that's because I've not considered enough)
If you are passing any values into the as_view() method that are likely to change after the server starts, for example some function call or database query whose return value could change after some users use the website, it will be evaluated only once, while the urls are loaded.
Let's say you are passing in the current time like:
path('about/', GreetingView.as_view(greeting=timezone.now())),
That note simply says that the value of the attribute 'greeting' for GreetingView will stay the same for all requests even if the server runs for a month, since timezone.now() is called only once.
Such arguments are good for reusing a View class with minimal changes. It depends completely on your use case.
For example:
path('add-car/', AddView.as_view(form=AddCarForm)),
path('add-bus/', AddView.as_view(form=AddBusForm)),

Django / Python, calling a specific class / function on every user Request

I was looking over the Django documentation on a way to do this but didn't see anything, though I may have missed it as I'm not sure exactly where to look... I want to be able to perform a specific action on every user request, such as instantiating a class and calling one of its functions, however the only way I know of to do this now is to put it in each view function. Is there a better way to do this, any advice is appreciated.
You want to use Django's middleware functionality.

ModelAdmin thread-safety/caching issues

Ultimately, my goal is to extend Django's ModelAdmin to provide field-level permissions—that is, given properties of the request object and values of the fields of the object being edited, I would like to control whether or not the fields/inlines are visible to the user. I ultimately accomplished this by adding a can_view_field() method to the ModelAdmin and modifying the built-in get_form() and get_fieldset() methods to remove/exclude fields+inlines that the user does not have permissions (as determined by can_view_field()) to see. If you'd like to see the code, I placed it in a pastebin, since it's long and only somewhat relevant.
It works great...almost. I appear to have run into some sort of thread-safety or caching issue, where the state of the ModelAdmin object is being leaked from one request to another in a reproducible manner.
I'll illustrate the problem with a simple example. Suppose that I have a model whose ModelAdmin I have extended with the field-level permissions code. This model has two fields:
- public_field, which can be seen/edited by any staff member
- secret_field, which can only be seen/edited by superusers
In this case, the can_view_field() method would look like this:
def can_view_field(self, request, obj, field_name):
"""
Returns boolean indicating whether the user has necessary permissions to
view the passed field.
"""
if obj is None:
return request.user.has_perm('%s.%s_%s' % (
self.opts.app_label,
action,
obj.__class__.__name__.lower()
))
else:
if field_name == "public_field":
return True
if field_name == "secret_field" and request.is_superuser:
return True
return False
Test case 1: with a fresh server restart, if you first view the changelist form as a superuser, you see the form as should happen, with both public_field and secret_field visible. If you log out and view it as a staff member (but not superuser), you only see public_field.
Test case 2: with a fresh server restart, if you log in as a staff member first, you still only see public_field. However, if you then log out and view as a superuser, you do not see secret_field. This is 100% reproducible.
I've done some basic thread-safety diagnostics:
At the end of get_form(), I've printed out the memory address of the ModelForm object. As it should be, it is unique with each request. Therefore, the ModelForm object is not the problem.
Immediately before the admin registration, I tried printing the memory address of the ModelAdmin object. In test case 1, it is unique with both requests. However with test case 2, it does not print at all on the second request.
At this point, I'm clueless. My next point of research will be the admin registration system (which I admittedly know nothing about). The state resets with a server restart, so it seems that the ModelAdmin must be cached? Or is it a thread-safety issue? If I turn it into a factory and return a deepcopy() of the ModelAdmin, would it serve a fresh ModelAdmin with each request? I'm clueless and would appreciate any thoughts. Thanks!
I'm confused about why you think ModelAdmin should be a new instance on each request. The admin objects are instantiated by the admin.site.register(Model) calls in each admin.py, which in turn is called from admin.autodiscover() in urls.py. In other words, this happens on process startup. Given the dynamic multi-process nature of most web serving environments, you may or may not get a new process with any particular request - certainly you won't get one every single time.
Because of this, it's not wise to store or alter state on a global object like ModelAdmin. I haven't looked through your linked code properly, but there was at least one case where you were altering an attribute on self as a result of a method call. Don't do that - you'll need to find some other way of passing dynamic values between methods.

URLs and side effects (Django)

I'm wondering if it's considered okay (particularly, in Django) to have a URL that's only intended for actions with side effects, that's only intended to be accessed by POST, and that is basically invisible to the user. Let's say, for the sake of making this concrete, I have a little messaging system on my site, and from their inbox, a user should be able to do a bunch of things like:
Delete a message
Mark a message as read
Report a message as spam
With all of those things causing a page refresh, but leading back to the same page. I'm wondering how to design my URLs and views around this. I see (at least) two options, and I have no idea which is more idiomatic.
Option 1)
Have a separate URL and view for each action. So, /inbox/delete-message/ maps to views.delete_message, and so on. At the end of each of those views, it redirects back to /inbox/.
I like the way things are clearly separated with this option. If a user somehow finds themselves sending a GET request to /inbox/delete-message/, that presents a sort of weird situation though (do I throw up an error page? silently redirect them?).
Option 2)
Use the same URL and view for each action, and have a POST parameter that identifies the action. So I would have one rather long inbox view, which would have a bunch of if statements testing whether request.POST['action'] == 'delete', or request.POST['delete'] == 'true' or whatever.
This option feels less clean to me, but I also feel like it's more common.
Which would be preferred by Djangonauts? Or is there another option that's better than either of the above?
A modified option #1 is the best approach. Consider this: suppose we weren't talking about a web app, but instead were just designing an inbox class. Which do you like better, a number of methods (delete_message(), mark_as_spam(), etc), or one big method (do_stuff(action))? Of course you would use the separate methods.
A separate URL for each action, each with a separate view, is far preferable. If you don't like the redirect at the end, then don't use it. Instead, have a render_inbox(request) method that returns an HttpResponse, and call the method at the end of each of your views. Of course, redirecting after a POST is a good way to prevent double-actions, and always leaves the user with a consistent URL.
Even better might be to use Ajax to hide the actions, but that is more involved.
I don't think there's anything wrong with either option, but #2 is potentially better from a performance standpoint. After the action is posted you can render the inbox without a redirect, so it cuts down on the HTTP traffic.
If you're writing a web 2.0 messaging app, you would be using AJAX calls and wouldn't be loading a new page at all. The process would proceed like so:
User clicks [delete] for a message. This button has a javascript action bound to it. This action does the following:
i. Change the UI to indicate that something is happening (grey the message or put up an hourglass).
ii. Send a request to /messages/inbox/1234/delete. (where 1234 is some identifier that indicates which message)
iii. When the response from the server comes back, it should indicate success or failure. Reflect this status in the current UI. For example, on success, refresh the inbox view (or just remove the deleted item).
On the server side, now you can create a URL handler for each desired action (i.e. /delete, /flag, etc.).
If want to use an even more RESTful approach, you would use the HTTP action itself to indicate the action to perform. So instead of including delete in your URL, it would be in the action. So instead of GET or POST, use DELETE /messages/inbox/1234. To set a flag for having been read, use SET /messages/inbox/1234?read=true.
I don't know how straightforward it is in Django to implement this latter recommendation, but in general, it's a good idea utilize the protocol (in this case HTTP), rather than work around it by encoding your actions into a URL or parameter.
I agree that #2 is a better approach.
But take care with overloading the submit <input /> with different methods -- if a user is using it with keyboard input and hits enter, it won't necessarily submit the <input /> you're expecting. Either disable auto-submit-on-enter, or code things up so that if there is more than one thing that submit can do, there's another field that sets what the action should be (eg a 'delete' checkbox, which is tested during a request.POST)
If you went with #1 I'd say that a GET to a POST-only view should be met with a 405 (method not supported) - or, failing that, a 404.

Django - alternative to subclassing User?

I am using the standard User model (django.contrib.auth) which comes with Django. I have made some of my own models in a Django application and created a relationship between like this:
from django.db import models
from django.contrib.auth.models import User
class GroupMembership(models.Model):
user = models.ForeignKey(User, null = True, blank = True, related_name='memberships')
#other irrelevant fields removed from example
So I can now do this to get all of a user's current memberships:
user.memberships.all()
However, I want to be able to do a more complex query, like this:
user.memberships.all().select_related('group__name')
This works fine but I want to fetch this data in a template. It seems silly to try to put this sort of logic inside a template (and I can't seem to make it work anyway), so I want to create a better way of doing it. I could sub-class User, but that doesn't seem like a great solution - I may in future want to move my application into other Django sites, and presumably if there was any another application that sub-classed User I wouldn't be able to get it to work.
Is the best to create a method inside GroupMembership called something like get_by_user(user)? Would I be able to call this from a template?
I would appreciate any advice anybody can give on structuring this - sorry if this is a bit long/vague.
First, calling select_related and passing arguments, doesn't do anything. It's a hint that cache should be populated.
You would never call select_related in a template, only a view function. And only when you knew you needed all those related objects for other processing.
"Is the best to create a method inside GroupMembership called something like get_by_user(user)?"
You have this. I'm not sure what's wrong with it.
GroupMembership.objects.filter( user="someUser" )
"Would I be able to call this from a template?"
No. That's what view functions are for.
groups = GroupMembership.objects.filter( user="someUser" )
Then you provide the groups object to the template for rendering.
Edit
This is one line of code; it doesn't seem that onerous a burden to include this in all your view functions.
If you want this to appear on every page, you have lots of choices that do not involve repeating this line of code..
A view function can call another function.
You might want to try callable objects instead of simple functions; these can subclass a common callable object that fills in this information.
You can add a template context processor to put this into the context of all templates that are rendered.
You could write your own decorator to assure that this is done in every view function that has the decorator.

Categories

Resources