Django - add context to request to be used by the view - python

Using Django I want to implement some middleware that will calculate some context that is to be used by the view itself.
For example, I have a middleware that looks at the request, and adds the user's permissions to the request, or some user configuration. The view looks at these permissions and decides how to handle the request using it.
This saves the need for multiple views (and multiple parts within the view) to query for this information.
I'm wondering what is the correct way to do that. One option is to just add request.user_permissions=... directly on the request. But is there some documented and expected way to do that?

There's no real documented way to do that, but Middleware is the correct place to do it and just adding properties to the request object is also the correct way.
You can confirm this, because Django is already doing it:
LocaleMiddelware
AuthenticationMiddleware
RemoteUserMiddleware
CurrentSiteMiddleware
SessionMiddleware
So just pick whatever is the most convenient data structure for your use case and tack that on to the request object.

This is not a perfect answer but at my experience I use this code. Every permission is saved in a boolean value which is true or false. You can access it in a html template like.
{% if request.user.is_admin %}
"Your code here"
{% else %}
"Your code here"
{% endif %}
and to send extra context you should create and pass an dicionary and pass it as as an argument to the render method from the view.
For eg:
def view(request, slug):
context = {'administrator':True}
blog_post = get_object_or_404(BlogPost, slug=slug)
context['blog_post'] = blog_post
return render(request, 'blog/detail_blog.html', context)
and access it like
{% if context.administrator %}
"Your code here"
{% else %}
"Your code here"
{% endif %}

I believe, since your middleware will calculate context, it should be implemented as context processor.
https://docs.djangoproject.com/en/3.1/ref/templates/api/#using-requestcontext
https://docs.djangoproject.com/en/3.1/ref/templates/api/#writing-your-own-context-processors

Related

Django CreateView with get_success_url not working for this specific case

I'm using Django 2.1.
I'm having a problem with a CreateView because I need to redirect to the update url, but that url contains one argument that is created manually after verifying that the form is valid.
This is the view code:
class ProjectCreateInvestmentCampaignView(LoginRequiredMixin, SuccessMessageMixin, generic.CreateView):
template_name = 'webplatform/project_edit_investment_campaign.html'
model = InvestmentCampaign
form_class = CreateInvestmentCampaignForm
success_message = 'Investment campaign created!'
def get_success_url(self):
return reverse_lazy('project-update-investment-campaign',
args=(self.kwargs['pk'], self.object.campaign.pk, self.object.pk))
def form_valid(self, form):
project = Project.objects.get(pk=self.kwargs['pk'])
form.instance.investment_type = "A"
form.instance.contract_type = "CI"
form.instance.history_change_reason = 'Investment campaign created'
valid = super(ProjectCreateInvestmentCampaignView, self).form_valid(form)
if valid:
campaign = CampaignBase.objects.create(project=project, )
form.instance.campaign = campaign
form.instance.campaign.project = project
form.instance.campaign.creation_date = timezone.now()
form.save()
return valid
As you can see, on the form_valid I validate first the form, and then I create the object campaign and assign all the related data. This is working fine.
The problem came when I changed the get_success_url to fit my use case, that is redirecting to the update view.
I debugged and saw that at the moment I create the variable valid on the form_valid, it checks the success url, and that triggers me the following error:
Exception Type: AttributeError
Exception Value:
'NoneType' object has no attribute 'pk'
Exception Location: /Volumes/Archivos/work/i4b/webplatform/views/investor_campaign_views.py in get_success_url, line 25
I asume that the error is because the campaign is not created yet so it's trying to get the pk from a non existing object.
The thing is that I cannot create the campaign if the form is not validated, but I need the campaign to make the url working (that url is working as it is on the UpdateView that I already have).
It will only invoke get_success_url after form_valid. So it's up to form_valid to create and save the objects needed. If it's valid for them not to be created, you need a different approach. Maybe initialize (say) self.campaign_pk = 0, update it if a campaign can be created with the pk of the campaign object, and let the next view sort out what to do when pk==0. Or,
...
args=(self.kwargs['pk'],
self.object.campaign.pk if self.object.campaign else 0,
self.object.pk))
(I don't fully follow your code so I might be barking up the wrong tree here)
It may be that you don't want CreateView but FormView, which doesn't handle object creation for you, so you may find greater flexibility over how to process a valid form that nevertheless cannot be fully honoured all the time. Or even, just a plain old function-based view, in which you can process two or more forms and be far more able to decide on conditions that constitute non-validity even after all the forms have technically validated.
This is a function-based view structure I have used where I have two forms to process, and a fairly long but boring set of operations to do after BOTH forms validate:
def receive_view( request):
# let's put form instantiation in one place not two, and reverse the usual test. This
# makes for a much nicer layout with actions not sandwiched by "boilerplate"
# note any([ ]) forces invocation of both .is_valid() methods
# so errors in second form get shown even in presence of errors in first
args = [request.POST, ] if request.method == "POST" else []
batchform = CreateUncWaferBatchForm( *args, layout=CreateUncWaferBatchLayout )
po_form = CreateUncWaferPOForm( *args, layout = CreateUncWaferPOLayout, prefix='po')
if request.method != "POST" or any(
[ not batchform.is_valid(), not po_form.is_valid() ]):
return render(request, 'wafers/receive_uncoated.html', # can get this out of the way at the top
{'batchform': batchform,
'po_form': po_form,
})
#it's a POST, everything is valid, do the work
...
return redirect('appname:viewname', ...)
For me, get_success_url was not invoked as the form was not valid (was invalid) and I didn't know. You can override form_invalid(self, form) to control the behavior.
Also, consider this block of code to show any errors in your template
{% if form.errors %}
<div class="alert alert-danger" role="alert">
{% for field, errors in form.errors.items %}
{% for error in errors %}
<b>{{ field }}</b>: {{ error }}
{% endfor %}
{% endfor %}
</div>
{% endif %}

I want to use the if statement based on the existence of a web page

So basically i have a complicated scenario. I am current using Django to build a website and i have current made two apps. Both apps have almost identical fields. The field I would want to focus on though is the Information field(which they both have and which i have auto generated with the help of the Wikipedia model)
So the case here is that I want to create an if and else statement in the html in such a way that if the page i am hyperlinking to exists it would go to the link dealing with DetailView but if it doesnt exist I would redirected to the Create View
I should also note that the two apps have their names linked with the help of the foreign key but when i try to open information links using the same name , they gave me different pks
I dont feel like i explained my problem well enough but I hope someone can understand what i mean
UPDATE
ok I create the get function using
def get(self, request, *args, **kwargs):
try:
self.object = self.get_object()
except Http404:
return redirect('/create/')
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
but i dont know how to use the CreateView fuction i created instead of the link i put
This is the Detail View Html
{%extends "home.html"%}
{%block head_title%} {{block.super}}{%endblock head_title%}
{% block content%}
<!-- verify authentication -->
{% if request.user.is_authenticated%}
<h3>{{object.title}}</h3><br/>
{% endif %}
<ul type="disc">
<div class="container">
<li><b>Artist: </b>{{object.Summary}}</li>
<li><b>Genre: </b>{{object.Genre}}</li>
<li><b>Bio: </b><br>{{object.Bio}}</li>
EDIT
</div>
</ul>
{%endif%}
{% endblock %}
This is my model
from django.db import models
from django.conf import settings
from Blog.models import MainPage
from django.urls.base import reverse
from Blog.Retrieve import retriever
from django.db.models.signals import pre_save,post_save
import InfoPedia
class InfoPedia(models.Model):
user =models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE)
Name =models.ForeignKey(MainPage,on_delete=models.CASCADE)
Location =models.CharField(max_length= 50,null=True,blank=True)
Information =models.TextField(null=True,blank=True)
TrackListing=models.TextField(null=True,blank=True)
Published=models.BooleanField(default=True)
Timestamp=models.DateTimeField(auto_now=True)
Updated=models.DateTimeField(auto_now=True)
def get_absolute_url(self):
# return f"/Blog/{self.slug}"
return reverse('InfoPedia:DetailView', kwargs={"pk":self.pk})
class Meta:
ordering=["-Updated","-Timestamp"] #orranges in order of updated
def get_tracklist(self):
return self.TrackListing.split(",")
def Information_create_pre_save( instance, sender, **kwargs):
instance.Information=retriever(instance.Name)
def rl_post_save_reciever(sender, instance,created,*args,**kwargs):
print("saved")
print(instance.Timestamp)
pre_save.connect(Information_create_pre_save, sender=InfoPedia)
post_save.connect(rl_post_save_reciever, sender=InfoPedia)
An alternative - rather than checking the if/else in the HTML, just make all the links to the DetailView URL.
Then, in the get() handler for the DetailView, you perform a queryset lookup for the object. If no object is found, then instead of displaying the DetailView HTML, return to the user a 302 redirect (i.e. a temporary redirect) to the CreateView for that object. So all your if/else logic is in the view function or class, instead of HTML.

Context variable to be used in base.html (Django)

I need a certain context variable in my base.html. This is to contain a set of usernames, e.g. [name1, name2, name3, name4,]. If a logged in user's username is part of this list, I give said user certain preferential treatment and show something in the navbar.
To achieve this, I wrote a template tag:
from django import template
from django.db import models
from django.contrib.auth.models import User
register = template.Library()
VIPS = [name1, name2, name3, name4,]
#register.simple_tag
def verified(user):
return VIPS
register.simple_tag(verified)
And then in base.html, I added {% load verified %} at the top, and then:
{% if user.username in verified %}
<!-- do something -->
{% endif %}
This isn't working. What am I doing wrong? I suspect I've written my template tag incorrectly, but I've tried several, more complex approaches (in vain), at least this simpler one made logical sense to me.
My project's a legacy Django 1.5 project with Python 2.7.
You don't need the register.simple_tag(verified) line, as the #register decorator is already doing that.
However, you might consider a different approach to avoid additional processing in the template, assuming your user is coming from request.user...
#regsiter.assignment_tag(takes_context=True)
def check_user_is_verified(context):
user = context['request'].user
return user and user in vips
Then in your template:
{% check_user_is_verified as is_verified %}
{% if is_verified %}
{# whatever #}
{% endif %}
By leveraging an assignment tag, you can check if the user is verified once, and leverage the context variable you assign instead of having to perform the same list processing each time.
Another alternative is to use a cached property on a custom User object, or a "Profile" model that is linked to your User model via a OneToOneField.
from django.utils.functional import cached_property
class Profile(models.Model):
user = models.OneToOneField(User)
#cached_property
def is_verified(self):
# get the list of vips here
return self.user in vips
If your list of vips changes, just clear the cache key, which you could do via a signal or a Celery task, etc:
del profile_instance.is_verified
Now you have a very efficient property you can check anywhere in your code. My preference tends to be fat models, skinny views and dumb templates.

Why is the get_absolute_url() defined in the models.py?

I am trying to redo my app views with a Class Based Views(CBV) and stumbled across this function get_absolute_url() being defined in the models.py, generic editing views
I have created models and have never used this function before. Is this specific to CBVs?
It's not specific to CBV, you can use it anywhere in your application. It makes it much easier to get the url for a model instance without having to mess around with url resolving. Also it is much easier to get the definitive url for your object in a template when you can call get_absolute_url on the object itself. For example, if you are looping through a list of objects:
{% for post in blog_posts %}
read post
{% endfor %}
That said, there's nothing stopping you using the method in your view either:
post = BlogPost.objects.get(...)
url = post.get_absolute_url()
There's also is nothing at all special about the method though. You can write your own get_foo_url() if you like instead. For example, I wrote a blog post about a get_admin_url, a method to allow you to get the Django admin url to an object:
class Book(models.Model):
...
def get_admin_url(self):
content_type = ContentType \
.objects \
.get_for_model(self.__class__)
return reverse("admin:%s_%s_change" % (
content_type.app_label,
content_type.model),
args=(self.id,))
# {{ book.get_admin_url }}

How to pass information using an HTTP redirect (in Django)

I have a view that accepts a form submission and updates a model.
After updating the model, I want to redirect to another page, and I want a message such as "Field X successfully updated" to appear on this page.
How can I "pass" this message to the other page? HttpResponseRedirect only accepts a URL. I've seen this done before on other sites. How is this accomplished?
This is a built-in feature of Django, called "messages"
See http://docs.djangoproject.com/en/dev/topics/auth/#messages
From the documentation:
A message is associated with a User.
There's no concept of expiration or
timestamps.
Messages are used by the Django admin
after successful actions. For example,
"The poll Foo was created
successfully." is a message.
You can use django-flashcookie app
http://bitbucket.org/offline/django-flashcookie/wiki/Home
it can send multiple messages and have unlimited types of messages. Lets say you want one message type for warning and one for error messages, you can write
def simple_action(request):
...
request.flash['notice'] = 'Hello World'
return HttpResponseRedirect("/")
or
def simple_action(request):
...
request.flash['error'] = 'something wrong'
return HttpResponseRedirect("/")
or
def simple_action(request):
...
request.flash['notice'] = 'Hello World'
request.flash['error'] = 'something wrong'
return HttpResponseRedirect("/")
or even
def simple_action(request):
...
request.flash['notice'] = 'Hello World'
request.flash['notice'] = 'Hello World 2'
request.flash['error'] = 'something wrong'
request.flash['error'] = 'something wrong 2'
return HttpResponseRedirect("/")
and then in you template show it with
{% for message in flash.notice %}
{{ message }}
{% endfor }}
or
{% for message in flash.notice %}
{{ message }}
{% endfor }}
{% for message in flash.error %}
{{ message }}
{% endfor }}
I liked the idea of using the message framework, but the example in the django documentation doesn't work for me in the context of the question above.
What really annoys me, is the line in the django docs:
If you're using the context processor, your template should be rendered with a RequestContext. Otherwise, ensure messages is available to the template context.
which is incomprehensible to a newbie (like me) and needs to expanded upon, preferably with what those 2 options look like.
I was only able to find solutions that required rendering with RequestContext... which doesn't answer the question above.
I believe I've created a solution for the 2nd option below:
Hopefully this will help someone else.
== urls.py ==
from django.conf.urls.defaults import *
from views import *
urlpatterns = patterns('',
(r'^$', main_page, { 'template_name': 'main_page.html', }, 'main_page'),
(r'^test/$', test ),
== viewtest.py ==
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
def test(request):
messages.success( request, 'Test successful' )
return HttpResponseRedirect( reverse('main_page') )
== viewmain.py ==
from django.contrib.messages import get_messages
from django.shortcuts import render_to_response
def main_page(request, template_name ):
# create dictionary of items to be passed to the template
c = { messages': get_messages( request ) }
# render page
return render_to_response( template_name, c, )
== main_page.html ==
{% block content %}
{% if messages %}
<div>
{% for message in messages %}
<h2 class="{{message.tag}}">{{ message.message }}</h2>
{% endfor %}
</div>
{% endif %}
{% endblock %}
I have read and checked all answers, and it seems to me that the way to go now is using the messaging framework. Some of the replies are fairly old and have probably been the right way at the time of the posting.
There is a lot of solutions
1 Use Django-trunk version - it support sending messages to Anonymous Users
2 Sessions
def view1(request):
request.session['message'] = 'Hello view2!'
return HttpResponseRedirect('/view2/')
def view2(request):
return HttpResponse(request.session['message'])
3 redirect with param
return HttpResponseRedirect('/view2/?message=Hello+view2')
4 Cookies
Can you just pass the message as a query param oon the URL to which you're redirecting? It's not terribly RESTy, but it ought to work:
return HttpResponseRedirect('/polls/%s/results/?message=Updated" % p.id)
and have that view check for a message param, scrub it for nasties, and display it at the top.
I think this code should work for you
request.user.message_set.create(message="This is some message")
return http.HttpResponseRedirect('/url')
Take a look at Django's messages framework. http://docs.djangoproject.com/en/dev/ref/contrib/messages/#ref-contrib-messages
You could also have the redirect url be the path to an already parameterized view.
urls.py:
(r'^some/path/(?P<field_name>\w+)/$', direct_to_template,
{'template': 'field_updated_message.html',
},
'url-name'
),
views.py:
HttpResponseRedirect( reverse('url-name', args=(myfieldname,)) )
Note that args= needs to take a tuple.
The solution used by Pydev UA is the less intrusive and can be used without modifying almost nothing in your code. When you pass the message, you can update your context in the view that handles the message and in your template you can show it.
I used the same approach, but instead passing a simple text, passed a dict with the information in useful fields for me. Then in the view, updated context as well and then returned the rendered template with the updated context.
Simple, effective and very unobstrusive.
While all suggestions so far work, I would suggest going with Ry4an's (pass it in the request URL) - just change the actual text to a coded text within a predefined set of text messages.
Two advantages here:
Less chance of something hacking through your scrubbing of bad content
You can localize your messages later if needed.
The other cookie related methods.. well, they don't work if the browser doesn't support cookies, and are slightly more expensive.. But only slightly. They're indeed cleaner to the eye.

Categories

Resources