Can Flask have multiple blueprints on one view method? - flask

I am using Flask 1.x due to an OEM limitation. Is it possible to stack blueprint routes for one view method and does order matter?
I am trying to build an API and one of my thoughts was to try the following. The /main/event rout (no id) will GET all current events for the logged in person; or, POST will handle a new event submission form. The /main/event/<uuid:event_id> rout should GET the event with the id or DELETE the event based on id.
Instead POST events complain about a missing event_id parameter. Do I need to break the id and non-id actions into different view methods?
#main_blueprint.route('/main/event', methods=['GET', 'POST'])
#main_blueprint.route('/main/event/<uuid:event_id>', methods=['GET', 'DELETE'])
#login_required
def event(event_id):
...
Edit
I mentioned an incorrect version of Flask above (now corrected), I forgot which project I was working on. I am tied to an OEM enforced version of Python and modules.
I have been able to come close by adding defaults=... to the first route; basically faking a uuid4 if it is missing as in:
#main_blueprint.route('/main/event/', defaults={'event_id':'0'*32} methods=['GET', 'POST'])
#main_blueprint.route('/main/event/<uuid:event_id>', methods=['GET', 'DELETE'])
This seems to "work" but is it necessary?

Related

Django - Show history in admin working but only when actions take place in admin

In Django admin, when you are looking at a record there is a button called "history". When you click on this button, it tells you when the record was created and by whom and also each time a field was changed and by whom. I find this extremely useful. However, I noted that these only show up for actions that are done on the admin page. So when I change the record through a view, this is not displayed in that history. Is there an easy way to have my views store this information also so that it will all be visible from the admin page?
Thanks so much in advance for your help.
The admin app comes with a model - LogEntry. Every time you do something in the admin app, there is some code somewhere that saves a LogEntry. This is how the app works. For example in the changeform_view there is something like this:
def changeform_view(self, request, ...):
...
if request.method == "POST":
if all_valid(formsets) and form_validated:
# do some other stuff then
self.log_change(request, new_object, change_message)
def log_change(self, request, object, message):
"""
Log that an object has been successfully changed.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import CHANGE, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(object).pk,
object_id=object.pk,
object_repr=str(object),
action_flag=CHANGE,
change_message=message,
)
LogEntry.objects.log_action is where the log is created. Unfortunately, this doesn't happen anywhere else, unless you were to make it happen.
There's nothing stopping you from doing this though. You can create a LogEntry wherever you want.
Having said that, it might be a bit confusing since when you see a LogEntry you now no longer no if that is a change that has happened because of someone manually changing some data via the admin app, or a change that has occurred programatically. It would probably be a better idea to create your own Log model, and save logs where and when you want.
You can always display your Logs in your in the relevant admin view should you so wish. Something like this will do the trick:
class MyAdmin(admin.modelAdmin):
readonly_fields = (logs_field,)
def logs_field(self, instance):
logs = Log.objects.filter(object=instance)
return format_html_join(
'\n', "<p>{}: {}</p>",
((log.date, log.message) for log in logs)
)

Django REST - Serialize User.get_all_permissions

I am building an application with a Django Rest backend, and a VueJS front end and am working through authorization and authentication. I have the authentication working well, but am a bit stuck on letting the front end (VueJS) know what the user has authorization to do in terms of Add/Change/View/Delete for a model. For example, if a user cannot add a customer, I don't want to show the 'Add Customer button'.
Working through the Django docs, and solutions on StackOverflow, I believe the simplest way is to send the user's permissions from Django to VueJS.
The 'best'/'simplest' way I can see to get the permissions is with the following:
userModel = User.objects.get(request.user)
return User.get_all_permissions(userModel)
Where I am stuck is exactly where to put this logic and how to serialize it. Does the above belong in the View, Serializer, other? Up until now, I have only been working with Models (ModelSerializers and ModelViews), but I don't believe this falls into this category.
Thanks in advance...
You should add this logic to views, because the views are used to implement these kinds of logic.
Actually, you don't want to use serializers here, because of the response of .get_all_permissions() method is already in serialized form
Apart from that, your provided code is not good (it's clearly bad). It should be as below,
return request.user.get_all_permissions()
because, you'll get current logged-in user's instance through request.user, to get his/her permissions, you all need to call the get_all_permissions() method
Example
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
#permission_classes(IsAuthenticated, )
#api_view()
def my_view(request):
logged_in_user = request.user
return Response(data=logged_in_user.get_all_permissions())

Django URL and making a portion of it optional, but using the same class API View

So I have this API URL on the back-end and I am wondering how to make a portion of it optional.
url(r'^api/documents/(?P<id>[0-9]+)$', GetDocumentsAPIView.as_view(), name='documents'),
So two things can happen coming from the front-end.
1) When the user logs in, it brings them to the /home which lists all of their projects. Clicking on the project brings them /documents/85 where the number is the number of the project and it lists all the documents for that project. This is sent to the API URL /api/documents/85. This much is working fine.
2) The other option is the user will just click on a Documents link in the navbar, which will just bring them to /documents. This should just go to the API URL /api/documents/ and eventually onto the serializer.py where their most recent project is determined and the documents for that are returned and rendered in the front-end
This I can't get working. Not sure if I can do it with just one url(), or if I need two. Was hoping the one I posted would look at the (?P<id>[0-9]+)$ as optional and if nothing was there would return the same GetDocumentsAPIView, but this is not the case.
EDIT: Including the view I am using for testing at this point. Just print() to see if anything is being routed to the view.
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
class GetDocumentsAPIView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
print(kwargs.get('id'))
return Response('Test')
No you need to add a URL pattern for r'^api/documents/$', which can launch the same view by the way. Only inside the view you'll have to assume id is optional:
if kwargs.get('id'):
# return specific document
else:
# return list of documents
I would rename your first URL pattern to 'view_document' so you can use the name 'documents' on the other, more generic view.
Note: I think your URL structure is wrong. Reading your URL, according to REST, I would expect to see the document with id 85 when fetching documents/85. If you actually are listing the docs of a project, the URL should be projects/85/documents/ or if unspecified projects/documents.

How to share object between blueprint in Flask

I have two blueprints, such as auth and sprints, the structure is as follows:
auth
__init__.py
views.py
forms.py
sprints
__init__.py
views.py
forms.py
I want to share api object after login successfully
#auth.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
try:
jira_url = app.config["JIRA_URL"]
app_id = app.config["JIRA_APPID"]
api = GreenHopper(options={"server": jira_url, "appid": 37},
basic_auth=(form.username.data, form.password.data))
g.api = api
except JIRAError:
api = None
...
...
return redirect(request.args.get('next') or url_for('scrumworks.index'))
return render_template('auth/login.html', form=form)
On the sprints blueprint, I want to get the api object like codes below:
#scrumworks.route("/sprints")
#login_required
def show_sprints():
if current_user.admin:
sprints = g.api.sprints(37)
return render_template("scrumworks/sprints.html")
else:
flash("Only admin users can access this page")
return redirect(url_for(".index"))
When the codes run, it will complain AttributeError: '_AppCtxGlobals' object has no attribute 'api'.
It seems the life cycle of g is per request, not global, so I can't store api in g
Another choice is session, but the api object cannot be serializable, so it can't be stored in session, what should I do next?
Any ideas? Thanks :-)
The g object is only available between requests.
The quick and dirty way to solve this, is to add your api object into the global space where the Flask app is defined and then importing the api object from there.
Keep in mind that with multiple servers and concurrency, this object can get out of sync fairly quickly and should remain stateless.
See this project on github for an example
The preferred solution for this is to use a database that fits the needs of your object. Redis, MongoDB and PostgreSQL are some of the more common ones.

Why Django 1.4 per-site cache does not work correctly with CACHE_MIDDLEWARE_ANONYMOUS_ONLY?

I am working on a Django 1.4 project and writing one simple application using per-site cache as described here:
https://docs.djangoproject.com/en/dev/topics/cache/#the-per-site-cache
I have correctly setup a local Memcached server and confirmed the pages are being cached.
Then I set CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True because I don't want to cache pages for authenticated users.
I'm testing with a simple view that returns a template with render_to_response and RequestContext to be able to access user information from the template and the caching works well so far, meaning it caches pages just for anonymous users.
And here's my problem. I created another view using a different template that doesn't access user information and noticed that the page was being cached even if the user was authenticated. After testing many things I found that authenticated users were getting a cached page if the template didn't print something from the user context variable. It's very simple to test: print the user on the template and the page won't be cached for an authenticated user, remove the user on the template, refresh the page while authenticated and check the HTTP headers and you will notice you're getting a cached page. You should clear the cache between changes to see the problem more clearly.
I tested a little more and found that I could get rid of the user in the template and print request.user right on the view (which prints to the development server console) and that also fixed the problem of showing a cached page to an authenticated user but that's an ugly hack.
A similar problem was reported here but never got an answer:
https://groups.google.com/d/topic/django-users/FyWmz9csy5g/discussion
I can probably write a conditional decorator to check if user.is_authenticated() and based on that use #never_cache on my view but it seems like that defeats the purpose of using per-site cache, doesn't it?
"""
A decorator to bypass per-site cache if the user is authenticated. Based on django.views.decorators.cache.never_cache.
See: http://stackoverflow.com/questions/12060036/why-django-1-4-per-site-cache-does-not-work-correctly-with-cache-middleware-anon
"""
from django.utils.decorators import available_attrs
from django.utils.cache import add_never_cache_headers
from functools import wraps
def conditional_cache(view_func):
"""
Checks the user and if it's authenticated pass it through never_cache.
This version uses functools.wraps for the wrapper function.
"""
#wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view_func(request, *args, **kwargs):
response = view_func(request, *args, **kwargs)
if request.user.is_authenticated():
add_never_cache_headers(response)
return response
return _wrapped_view_func
Any suggestions to avoid the need of an extra decorator will be appreciated.
Thanks!
Ok, I just confirmed my "problem" was caused by Django lazy loading the User object.
To confirm it, I just added something like this to my view:
test_var = "some text" + request.user
And I got an error message telling me I couldn't concatenate an str to a SimpleLazyObject. At this point the lazy loading logic hasn't got a real User object yet.
To bypass the lazy loading, hence return a non-cache view for authenticated users, I just needed to access some method or attribute to triggers an actual query on the User object. I ended up with this, which I think it's the simplest way:
bypass_lazyload = request.user.is_authenticated()
My conditional_cache decorator is no longer needed, although it was an interesting exercise.
I may not need to do this when I finish working with my views as I'll access some user methods and attributes on my templates anyway but it's good to know what was going on.
Regards.