In my app I have a bunch of DetailViews and ListViews. I need to add a panel with "last visited pages" info to every page. I know I can make two my own classes inheriting from DetailView and ListView respectively and overriding get_context_data method, but that will dublicate the code. At the moment I solved the problem, by simply adding one line of code to every view
context['panel_info'] = get_panel_info()
but I don't like this solution as well.
My idea was to create a Mixin with custom get_context_data method, but as far as I know, if I inherit like this
class MyView(DetailView, MyMixin):
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
return context
Mixin's method won't be called at all.
So what's the cleverest solution to this?
There are different ways to do this, if you need something reusable to all of your views then a context processor might be the solution, yet if you need it only on some specific views then a Mixin might also do the trick.
How you do it:
class MyMixin(object):
def get_context_data(self, **kwargs):
context = super(MyMixin, self).get_context_data(**kwargs)
context['panel_info'] = get_panel_info()
return context
class MyView(MyMixin, DetailView):
...
You don't need to override the get_context_data in your MyView for the Mixin to work.
Like BurhanKhalid mentioned, using a template context processor is a clever solution. Context processors let you specify a number of variables that get set in each context automatically.
The only catch is that you need to use RequestContext in place of Context. So,
def my_view(request):
return render(request, 'template.html', {'whatever': 'whatever'},
context_instance=RequestContext(request))
Make the context processor. Its basically a method which takes a HttpRequest and returns a dict.
def last_visited_pages_context_processor(request):
# Your custom code
return {'foo': 'bar'}
Now, just add the context processor to TEMPLATE_CONTEXT_PROCESSORS in your settings.py,
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'your_app.context_processors.last_visited_pages_context_processor',
)
Good to go. More info at http://www.djangobook.com/en/2.0/chapter09.html
Related
I am generating an object from a submitted form. I want to pass along the generated AutoField to the next form as a hidden form element. However, I cannot figure out how to pass a variable from post() to get_context_data(). I know that post() is called first, followed by get_context_data(), but adding the variable to self does not yield the results I expect (the attribute does not exist on self).
Here is an example of what I am experiencing:
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
print self.hello
return context
def post(self, request, *args, **kwargs):
self.hello = "hello"
return HttpResponseRedirect(request.path)
self.hello is not valid when called in get_context_data. I feel as though I may be mistaken with the HttpResponseRedirect(request.path) call, but I'm not sure how else to render the template.
Is it a simple mistake, or should I be approaching this an entirely different way?
By defining post yourself, you've overridden the default behaviour of the view. You can see that there is no call to get_context_data, or any of the other class methods, so naturally they won't be called.
Generally you should not be overriding the specific get or post methods. You haven't shown the full view so it's not clear what behaviour you are trying to achieve, but for example in a form view you would want to define success_url to set the place the form redirects to after submission.
You have to find a way to pass the object ID to the next page. The options that come to mind are to put it into the URL or as solarissmoke has suggested save it in the session. If you are doing it in the url you can also put the page sequence there (meaning 1 for the forst form, 2 for the second...).
The nice thing about this approach is, that you can cover all functonailty in one view: depending on the page set the respective fields in the get_object methods (self.fields=[....]) and the template names in the get_template_names method.
So using an Updateview, it would look like this:
urls.py:
....
url(r'^mysite/(?P<object_no>\d+)/(?P<form_no>\d+)$', BaseView.as_view()),
views.py:
class BaseView(UpdateView):
def get_object(self):
obj=MyModel.objects.get(id=self.kwargs['object_no'])
form_no = self.kwargs['form_no']
if form_no=="1":
self_fields=["field1","field2"...]
.....
def get_object(self):
obj=MyModel.objects.get(id=self.kwargs['object_no'])
form_no = self.kwargs['form_no']
if form_no=="1":
self_fields=["field1","field2"...]
.....
return obj
def get_template_names(self):
from_no = self.kwargs['form_no']
if form_no=="1":
return ["template1.html"]
....
You have to make sure that all your fields can be null.
I've been learning Django and one source of confusion I have is with class based views and when to override the get method. I've looked through the documentation and it explains what get does but it doesn't explain when I should override get.
I originally created a view this way:
class ExampleView(generic.ListView):
template_name = 'ppm/ppm.html'
paginate_by = 5
def get(self, request):
profiles_set = EmployeeProfile.objects.all()
context = {
'profiles_set': profiles_set,
'title': 'Employee Profiles'
}
return render(request, self.template_name, context)
But I was recently told that my code was simple of enough for the default implementation, and that all I needed was this:
class ExampleView(generic.ListView):
model = EmployeeProfile
template_name = 'ppm/ppm.html'
So my Question is this: In what scenario/circumstance should I override the get method?
If you are using the builtin generic views, then you should rarely have to override get(). You'll end up either duplicating lots of functionality, or break features of the view.
For example, the paginate_by option will no longer work in your view, because you are not slicing the queryset in your get() method.
If you are using a generic class based view like ListView, you should try to override specific attributes or methods where possible, rather than overriding get().
The advantage of your view which overrides get() is that it's very clear what it does. You can see that the view fetches a queryset, includes it in a context, then renders the templates. You don't need to know about ListView to understand the view.
If you like the explicitness of overriding get() subclass View instead. You aren't using any of the features of ListView, so it doesn't make sense to subclass it.
from django.views.generic import View
class ExampleView(View):
template_name = 'ppm/ppm.html'
def get(self, request):
...
You should override the get method when you specifically want to do something other than the default view does. In this case, your code isn't doing anything other than rendering the template with the list of all EmployeeProfile objects, which is exactly what the generic ListView would do.
You might override it if you want to do something more complicated. For example, maybe you want to filter based on a URL parameter:
class ExampleView(generic.ListView):
template_name = 'ppm/ppm.html'
def get(self, request):
manager = request.GET.get('manager', None)
if manager:
profiles_set = EmployeeProfile.objects.filter(manager=manager)
else:
profiles_set = EmployeeProfile.objects.all()
context = {
'profiles_set': profiles_set,
'title': 'Employee Profiles'
}
return render(request, self.template_name, context)
Im building a Template Context Processor to call the PK of each page that I have and that PK call it in the base.html, I have achieve to do a Template Context Processor with other query, but now I need to pass the PK. The context p works very well, but the context edit it does not, how can I call the PK from a Function Based View?
For example: localhost:8000/path/8 , I need to pass 8 in the context edit
def my_processor(request):
context = {'edit':InfoPredioGeneral.objects.filter(pk=self.kwargs['pk']),
'p':InfoPredioGeneral.objects.filter(user_id=request.user).latest('id')}
return context
I know that it does not works because of self and kwargs are not defined. But how can I do that?
You are using a FBV instead of a CBV, so the self you are using should be used with class methods, answering your question, you should pass the id parameter in the view, do something like this:
def my_processor(request, id):
context = {'edit':InfoPredioGeneral.objects.filter(pk=id),
'p':InfoPredioGeneral.objects.filter(user_id=request.user).latest('id')}
return context
in your urls.py you should put something like this:
url(r'^path/(?P<id>\d+)', my_processor, name='my_processor')
https://github.com/AnthonyBRoberts/fcclincoln/blob/master/apps/story/views.py
I'm a little embarrassed to admit that this is mine. But it is.
class FrontpageView(DetailView):
template_name = "welcome_content.html"
def get_object(self):
return get_object_or_404(Article, slug="front-page")
def get_context_data(self, **kwargs):
context = super(FrontpageView, self).get_context_data(**kwargs)
context['slug'] = "front-page"
events = Article.objects.filter(slug="events")
context['events'] = events
return context
So this is a pretty normal class-based detail view in Django.
It's assigning a template, getting an Article object, and adding some things to the context_data.
Then I copied this class 17 times. Each time, there's a different template, and a different slug, and different stuff added to the context_data.
The idea is that there's a WYSIWYG editor for administrators to change the web content, and a user authentication system, to allow multiple people access to the site content. Basically, a super-simple CMS, so no one has to edit html to update the site.
But I really wish I could refactor this so I don't have these nearly identical 18 classes. Any suggestions on where I should start on this would be most welcome.
Squash all of your classes down to a single class that inherits from TemplateResponseMixin, as DetailView does, (also check out the SingleObjectTemplateResponseMixin) and override its get_template_names() method to return the template appropriate for the current situation.
A beautiful example of this being used is in the django-blog-zinnia project
def get_template_names(self):
"""
Return a list of template names to be used for the view.
"""
model_type = self.get_model_type()
model_name = self.get_model_name()
templates = [
'zinnia/%s/%s/entry_list.html' % (model_type, model_name),
'zinnia/%s/%s_entry_list.html' % (model_type, model_name),
'zinnia/%s/entry_list.html' % model_type,
'zinnia/entry_list.html']
if self.template_name is not None:
templates.insert(0, self.template_name)
return templates
Django will take that list of names and try each item to see if it exists in the templates folder. If it does, that template is used.
Update
After looking at your code a little more closely, perhaps something like this:
In your main urls.py
# convert each url
url(r'^$', FrontpageView.as_view()),
url(r'^history/$', HistoryView.as_view()),
url(r'^calendar/$', CalendarView.as_view()),
url(r'^news/$', NewsView.as_view()),
url(r'^visitors/$', VisitorsView.as_view()),
...
# to just
url(r'^(?P<slug>[\w\d/-]+)/$', SuperSpecialAwesomeView.as_view()),
# but, put this at the end of urls list after any routes that don't use this view
DetailView, after setting the class attribute model, will check to see if slug is in the url's kwargs and if it is, it will use the slug to do a model lookup just like what you are already doing: Article.ojects.get(slug=self.kwargs['slug'])
models.py
You could add a type field to your Article model. The type will specify what type of article it is. For example, your ChildrenView, YouthView, and AdultView could all have a type of music (since the templates are all music, I'm assuming that's how they are related).
ARTICLE_TYPE_CHOICES = (
(0, 'music'),
(1, 'weddings'),
(2, 'outreach'),
...
)
class Article(models.Model):
...
type = models.IntegerField(choices=ARTICLE_TYPE_CHOICES)
...
Then, in your views.py
class SuperSpecialAwesomeView(DetailView):
template_name = None
model = Article
def get_template_names(self):
slug = self.kwargs.get('slug', '')
templates = [
# create a template based on just the slug
'{0}.html'.format(slug),
# create a template based on the model's type
'{0}.html'.format(self.object.get_type_display()),
]
# Allow for template_name overrides in subclasses
if self.template_name is not None:
templates.insert(0, self.template_name)
return templates
Given an article instance with a type of music and a slug of ministry/children, Django will look for a template named ministry/children.html and a template named music.html.
And if you need to do some special stuff for other views (like you will probably need to for SermonsView), then subclass SuperSpecialAwesomeView
class SermonsView(SuperSpecialAwesomeView):
paginate_by = 2
queryset = Article.objects.order_by('-publish_date')
A quick approach I would think:
Add a template field in the model with a list of predefined template choices (those can be created dynamically).
Override the default DetailView methods, override the get_template_names method to assign the proper template to the view (if not available fallback, that can be done through a try: except:).
Apart from that you can alter the View behaviour with any kind of model flags.
This way you can have a single entry point for a model, rather than defining repeatable views all over the place.
I tend to keep a FrontPageView independent from other views though, for easiness and because it serves a different purpose.
If you need repeatable context entries, consider a context processor, if you need repeatable context entries for specific views consider Mixins.
Rarely I can find a places I need to use CBD.
You can refactor it like this:
def editable_page(slug):
return {
'context': {
'slug': slug
}
'template': 'mysupertemplates/{0}.html'.format(slug)
}
def frontpage(req):
return editable_page('frontpage')
def chat(req):
return editable_page('char')
def about(req):
return editable_page('about')
Basically what I'm trying to achieve is a multi-model django app where different models take advantage of the same views. For example I've got the models 'Car' 'Make' 'Model' etc and I want to build a single view to perform the same task for each, such as add, delete and edit, so I don't have to create a seperate view for add car, ass make etc. I've built a ModelForm and Model object for each and would want to create a blank object when adding and a pre-populated form object when editing (through the form instance arg), with objects being determined via url parameters.
Where I'm stuck is that I'm not sure what the best way to so this is. At the moment I'm using a load of if statements to return the desired object or form based on parameters I'm giving it, which get's a bit tricky when different forms need specifying and whether they need an instance or not. Although this seems to be far from the most efficient way of achieving this.
Django seems to have functions to cover most repetitive tasks, is there some magic I'm missing here?
edit - Here's an example of what I'm doing with the arguments I'm passing into the url:
def edit_object(request, object, id):
if(object==car):
form = carForm(instance = Car.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
What about using Class Based Views? Using CBVs is the best way in Django to make reusable code. For this example maybe it can be a little longer than function based views, but when the project grows up it makes the difference. Also remember "Explicit is better than implicit".
urls.py
# Edit
url(r'^car/edit/(?P<pk>\d+)/$', EditCar.as_view(), name='edit-car'),
url(r'^make/edit/(?P<pk>\d+)/$', EditMake.as_view(), name='edit-make'),
# Delete
url(r'^car/delete/(?P<pk>\d+)/$', DeleteCar.as_view(), name='delete-car'),
url(r'^make/delete/(?P<pk>\d+)/$', DeleteMake.as_view(), name='delete-make'),
views.py
class EditSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-edit.html'
class EditCar(EditSomethingMixin, UpdateView):
model = Car
form_class = CarForm
class EditMake(EditSomethingMixin, UpdateView):
model = Make
form_class = MakeForm
class DeleteSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-delete.html'
class DeleteCar(DeleteSomethingMixin, DeleteView):
model = Car
class DeleteMake(DeleteSomethingMixin, DeleteView):
model = Make
Just pass your class and form as args to the method then call them in the code.
def edit_object(request, model_cls, model_form, id):
form = model_form(instance = model_cls.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
then just pass in the correct classes and forms in your view methods
def edit_car(request,id):
return edit_object(request, Car, CarForm, id)
each method knows what classes to pass, so you eliminate the if statements.
urls.py
url(r'^car/delete/(?<pk>\d+)/', edit, {'model': Car})
url(r'^make/delete/(?<pk>\d+)/', edit, {'model': Make})
views.py
def edit(request, id, model):
model.objects.get(id=id).delete()