I'm trying to extend a simple date based view (using the 1.3 generic class approach) with my own mixin "BaseViewMixin":
class BaseViewMixin(object):
"""define some basic context for our views"""
model = Alert
month_format = "%m"
date_field = "date"
def get_context_data(self, **kwargs):
"""extra context"""
context = super(BaseViewMixin, self).get_context_data(**kwargs)
context["CACHE_SERVERS"] = settings.CACHE_SERVERS
return context
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(BaseViewMixin, self).dispatch(*args, **kwargs)
class IndexView(BaseViewMixin, TodayArchiveView):
template_name= "index.html"
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
queryset = Alert.objects.default(self.day)
tickets = Alert.objects.tickets(self.day)
alert_groups = []
for item in tickets:
alert_groups.append({"ticket": item, "alerts": queryset.filter(ticket=item["ticket"])})
context["alert_groups"] = alert_groups
return context
The problem is that all of the date based things you normally get in the context are wiped out as soon as I override the get_context_data method for my IndexView class. Why is that? I would expect {{ day }}, {{ previous_day }} etc to show up in the context, as well as self. When I remove my get_context_data method all the generic date stuff works.
urls.py entry is just:
url(r'^$', IndexView.as_view(), name='index'),
This is one way to do it which allows me to reuse code. I'm still not understanding why get_context_data wipes out the TodayArchiveView context the way I define it in my original question. It seems that using the scenario below would do the same thing to my mixin but it doesn't. The ViewMixin context is preserved when calling get_context_data in DateMixin.
class ViewMixin(object):
"""define some basic context for our views"""
model = Alert
def get_context_data(self, **kwargs):
"""extra context"""
context = super(ViewMixin, self).get_context_data(**kwargs)
context["CACHE_SERVERS"] = settings.CACHE_SERVERS
return context
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ViewMixin, self).dispatch(*args, **kwargs)
class DateMixin(object):
month_format = "%m"
date_field = 'date'
def get_alert_groups(self):
none, qs, dated_items = self.get_dated_items()
day = dated_items["day"]
queryset = Alert.objects.default(day)
tickets = Alert.objects.tickets(day)
alert_groups = []
for item in tickets:
alert_groups.append({"ticket": item, "alerts": queryset.filter(ticket=item["ticket"])})
return alert_groups
def get_context_data(self, **kwargs):
context = super(DateMixin, self).get_context_data(**kwargs)
context["alert_groups"] = self.get_alert_groups()
return context
class IndexView(ViewMixin, DateMixin, TodayArchiveView):
template_name= "index.html"
Not an exact answer to your question, but considering the fact that your goal seems to be adding a certain context to more than one view a context processor might be a good solution!
The problem arise because you're inhereting from 2 classes.
context = super(IndexView, self).get_context_data(**kwargs) will initialise the context with the method from BaseViewMixin not TodayArchiveView (super walks base classes from left to right) --> context variables from TodayArchiveView will be lost.
I think you can do this:
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context_day = super(BaseViewMixin, self).get_context_data(**kwargs)
context = dict(context, **context_day)
...
Related
I need your help because I have approximately 6 views having common data.
Example similar to my problem :
def informationPlayers(request,nameTeam):
try :
team = Team.objects.get(name = nameTeam)
except Team.DoesNotExist:
return redirect(teams)
if not request.user.is_authenticated():
formLogin = loginForm(auto_id=False)
countMembers = team.members.count()
else :
members = team.members.members()
... code specific to this view ...
def informationCoach(request,nameTeam):
try :
team = Team.objects.get(name = nameTeam)
except Team.DoesNotExist:
return redirect(teams)
if not request.user.is_authenticated():
formLogin = loginForm(auto_id=False)
countMembers = team.members.count()
else :
members = team.members.members()
... code specific to this view ...
So these 2 views have same variables and an algorithm (if user is authenticated or not).
I don't want to write this algortihm and variables in all views using them, How can I do please ?
I already try TEMPLATE_CONTEXT_PROCESSORS but it's applied in all views/pages of my site, I don't want that.
Here is an answer using class base views. Classes in Python (and any other language that supports them) is a good way to share code while making it obvious to any other developer which views should and should not share this code.
Your urls.py should be kept about the same, except that you will need to call the class methods. Something like:
from django.conf.urls import *
from .views import *
urlpatterns = [
url(r'^(?P<nameTeam>[^/]+)/player/$', PlayerView.as_view(), name='player_view'),
url(r'^(?P<nameTeam>[^/]+)/coach/$', CoachView.as_view(), name='player_view'),
]
and then on your views.py, you would do something like:
class TeamAccessMixin(object):
# This is really just a regular Python object
team = None
formLogin = None
countMembers = None
members = None
def get_team(self, teamName):
try :
self.team = Team.objects.get(name = nameTeam)
except Team.DoesNotExist:
return redirect(self.teams)
if not self.request.user.is_authenticated():
self.formLogin = loginForm(auto_id=False)
self.countMembers = team.members.count()
else :
self.members = team.members.members()
class PlayerView(TeamAccessMixin, DetailView):
model = Player
template_name = 'team/player.html'
def get_context_data(self, **kwargs):
context = super(PlayerView, self).get_context_data(**kwargs)
self.get_team(kwargs['teamName']);
context['team'] = self.team
context['members'] = self.members
# Special code to add additional context specific to this view
...
return context
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(PlayerView, self).dispatch(request, *args, **kwargs)
class CoachView(TeamAccessMixin, DetailView):
model = Coach
template_name = 'team/coach.html'
def get_context_data(self, **kwargs):
context = super(CoachView, self).get_context_data(**kwargs)
self.get_team(kwargs['teamName']);
context['team'] = self.team
context['members'] = self.members
# Special code to add additional context specific to this view
...
return context
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(CoachView, self).dispatch(request, *args, **kwargs)
Just answering your specific question, but I also see you may not understand other aspects of Django (like login decorators, which will ensure you users are logged in or will automatically redirect them). So, as an added bonus, here is how I would have implemented what you are trying to do (assuming some details you are not specifying):
class TeamAccessMixin(object):
# This is really just a regular Python object
def get_object(self, queryset=None):
# It is best practice to simply give a 404 if the record does not exist
object = get_object_or_404(Team, slug=self.kwargs['nameTeam'])
return object
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
# the decorator will ensure all users are logged in or will redirect to login page
return super(TeamAccessMixin, self).dispatch(request, *args, **kwargs)
class PlayerView(TeamAccessMixin, DetailView):
model = Player
template_name = 'team/player.html'
def get_context_data(self, **kwargs):
context = super(PlayerView, self).get_context_data(**kwargs)
context['team'] = self.object
context['members'] = self.object.members
# Special code to add additional context specific to this view
...
return context
class CoachView(TeamAccessMixin, DetailView):
model = Player
template_name = 'team/coach.html'
def get_context_data(self, **kwargs):
context = super(CoachView, self).get_context_data(**kwargs)
context['team'] = self.object
context['members'] = self.object.members
# Special code to add additional context specific to this view
...
return context
Thank you #dkarchmer, I did something like that :
version modified :
class ProjetMixin(object) :
project = None
coms = None
form = None
def get_context_data(self, *args, **kwargs) :
context = super(ProjetMixin, self).get_context_data(*args, **kwargs)
try :
self.project = Project.objects.get(name = self.kwargs['name']) #in urls
except Project.DoesNotExist:
return redirect(projects)
self.coms = self.project.coms_set.order_by('-date_send')
if not self.request.user.is_authenticated():
self.form = formForm(auto_id=False)
context['project'] = self.project
context['coms'] = self.coms
context['form'] = self.form
return context
class FicheProjetView(ProjetMixin, TemplateView):
template_name = 'path/homeProject.html'
def get_context_data(self, **kwargs):
context = super(FicheProjetView, self).get_context_data(**kwargs)
if self.request.POST :
...
return context
def dispatch(self, request, *args, **kwargs):
return super(FicheProjetView, self).dispatch(request, *args, **kwargs)
It's proper ?
I want to put info about one object in many views without repeating it in get_context_data in each view. As u understand i need a class with get_context_data inside, that i can mix with other views.
Here in my example i want to see 'some_object' in context of UpdateAnotherObjectView:
class BaseObjectInfoView(View):
def get_context_data(self, **kwargs):
context_data = super(BaseObjectInfoView, self).get_context_data(**kwargs)
context_data['some_object'] = SomeObjects.objects.get(pk=1)
return context_data
class UpdateAnotherObjectView(BaseObjectInfo, UpdateView):
template_name = 'create_object.html'
form_class = AnotherObjectForm
model = AnotherObjects
def get_context_data(self, **kwargs):
context_data = super(UpdateAnotherObjectView, self).get_context_data(**kwargs)
context_data['all_another_objects'] = AnotherObjects.objects.all()
return context_data
it works, but get_context_data is not a part of parent 'View' class. May be i need more special class to inherit from in BaseObjectInfoView?
or maybe better to construct context with another method ?
Mixins don't need to be views, but it helps IDE's if they have the methods they're overriding.
Contexts are handled by django.views.generic.base.ContextMixin (details on this very handy site), So the class-based views way of things would be this:
from django.views import generic
class WebsiteCommonMixin(generic.base.ContextMixin):
page_title = ''
active_menu = None
def get_context_data(self, **kwargs):
context = super(WebsiteCommonMixin, self).get_context_data(**kwargs)
context.update(dict(page_title=self.page_title, active_menu=self.active_menu))
return context
class NewsListView(WebsiteCommonMixin, ListView):
page_title = 'News list'
active_menu = 'News'
model = News
paginate_by = 12
I do this for many projects and the simple views you have to create anyway, are fully declarative. And by simple, I mean that they can consist of mulitple mixins, all doing the hard stuff in either get_queryset, get_context_data or form_valid. More elaborate example, straight from a project:
class FeedbackMixin(object):
message = 'Well Done!'
def __init__(self):
self._message_kwargs = {}
super().__init__()
def add_message_kwarg(self, name, value) -> None:
self._message_kwargs[name] = value
def format_message(self, kwargs) -> str:
return self.message.format(**kwargs)
def generate_message(self) -> None:
msg = self.format_message(self._message_kwargs)
messages.success(getattr(self, 'request'), msg)
class ModelFeedbackMixin(FeedbackMixin, generic.edit.ModelFormMixin):
success_view_name = None
success_url_kwargs = None
def get_success_url_kwargs(self):
return self.success_url_kwargs
def get_success_url(self) -> str:
success_url_kwargs = self.get_success_url_kwargs()
if not self.success_view_name:
url = super().get_success_url()
elif success_url_kwargs is not None:
url = reverse(self.success_view_name, kwargs=success_url_kwargs)
else:
if hasattr(self.object, 'slug'):
url_kwargs = {'slug': self.object.slug}
else:
url_kwargs = {'pk': self.object.pk}
url = reverse(self.success_view_name, kwargs=url_kwargs)
return url
def form_valid(self, form):
response = super().form_valid(form)
self.generate_message()
return response
Maybe this way could be easier to read...
def add_context(func):
# this is a wrapper function
def wrapper(*args, **kwargs):
context_data = func(*args, **kwargs)
context_data['some_object'] = SomeObjects.objects.get(pk=1)
return context_data
return wrapper
class UpdateAnotherObjectView(BaseObjectInfo, UpdateView):
template_name = 'create_object.html'
form_class = AnotherObjectForm
model = AnotherObjects
#add_context
def get_context_data(self, **kwargs):
kwargs['all_another_objects'] = AnotherObjects.objects.all()
return kwargs
How can I get 'pk' or 'id' in get_context_data from CBV DetailView?
class MyDetail(DetailView):
model = Book
template_name = 'book.html'
def get_context_data(self, **kwargs):
context = super(MyDetail, self).get_context_data(**kwargs)
context['something'] = Book.objects.filter(pk=pk)
return context
url:
url(r'^book/(?P<pk>\d+)/$', MyDetail.as_view(), name='book'),
You can get it from self.kwargs['pk'].
I'm not sure why you want to, though, since the superclass already gets the Book corresponding to that pk - that's the whole point of a DetailView.
class MyDetail(DetailView):
model = Book
template_name = 'book.html'
def get_context_data(self, **kwargs):
context = super(MyDetail, self).get_context_data(**kwargs)
context['something'] =Book.objects.filter(pk=self.kwargs.get('pk'))
return context
self.kwargs['pk'] it doesn't work in Django 2.2
in DetailView
self.object is the object that this view is displaying.
So, to access the object's fields like id or pk just self.object.id or self.object.pk
So, The answer in Django 2.2 can be like:
class MyDetail(DetailView):
model = Book
template_name = 'book.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['something'] = Book.objects.filter(pk=self.object.pk) # <<<---
return context
Django 2.2 Doc
In get_context_data you already have the object in self.object (and you can do self.object.pk). Here's what happens upstream in the class hierarchy (DetailView inherits from BaseDetailView):
class BaseDetailView(SingleObjectMixin, View):
"""
A base view for displaying a single object
"""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
Reading Django source code to understand stuff is incredibly easy.
And by the way, I am not sure you can always rely on the fact that kwargs has a 'pk' key.
In addition to getting it from self.kwargs as Daniel Roseman suggested, you can use self.get_object().pk, for example if you change your URL identifier from pk to, say, slug or something.
you can simply get it in the 'get' method, like this:
def get_context_data(self, request, pk, *args, **kwargs):
context = super(MyDetail, self).get_context_data(**kwargs)
context['something'] =Book.objects.filter(pk=self.kwargs.get('pk'))
return context
def get_context_data(self, request, pk, *args, **kwargs):
context = super(MyDetail, self).get_context_data(**kwargs)
context['something'] =Book.objects.filter(pk=self.kwargs.get('pk'))
return context
Filter returns a query set that matches the lookup parameter (pk). Since 'pk' is unique, it would return the same results as get but for performance issues, ideally you'd want to use the get method to return one single object:
def get_context_data(self, request, pk, *args, **kwargs):
context = super(MyDetail, self).get_context_data(**kwargs)
context['something'] =Book.objects.get(pk=self.kwargs.get('pk'))
return context
I have a model like this:
class Job(models.Model):
slug = models.SlugField()
class Application(models.Model):
job = models.ForeignKey(Job)
And a view like this:
class ApplicationCreateView(CreateView):
model = Application
A user will view the job object (/jobs/<slug>/), then complete the application form for the job (/jobs/<slug>/apply/).
I'd like to pass application.job.slug as the initial value for the job field on the application form. I'd also like for the job object to be put in context for the ApplicationCreateView (to tell the user what job they're applying for).
How would I go about doing this in my view?
You may be interested in CreateView page of the fantastic http://ccbv.co.uk/ In this page, you can see in one glance which member methods and variables you can use.
In your case, you will be interested to override:
def get_initial(self):
# Call parent, add your slug, return data
initial_data = super(ApplicationCreateView, self).get_initial()
initial_data['slug'] = ... # Not sure about the syntax, print and test
return initial_data
def get_context_data(self, **kwargs):
# Call parent, add your job object to context, return context
context = super(ApplicationCreateView, self).get_context_data(**kwargs)
context['job'] = ...
return context
This has not been tested at all. You may need to play with it a little. Have fun.
I ended up doing the following in a function on my class:
class ApplicationCreateView(CreateView):
model = Application
form_class = ApplicationForm
success_url = 'submitted/'
def dispatch(self, *args, **kwargs):
self.job = get_object_or_404(Job, slug=kwargs['slug'])
return super(ApplicationCreateView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
#Get associated job and save
self.object = form.save(commit=False)
self.object.job = self.job
self.object.save()
return HttpResponseRedirect(self.get_success_url())
def get_context_data(self, *args, **kwargs):
context_data = super(ApplicationCreateView, self).get_context_data(*args, **kwargs)
context_data.update({'job': self.job})
return context_data
I need to display a detail page for a video with some other data.
For that I use DetailView that I have overridden to add some variables to the context.
Here are the code parts:
#urlconf
#...
(r'viewtube/(?P<pk>\d+)$', VideoFileDetailView.as_view()),
#...
#view
class VideoFileDetailView(DetailView):
model = VideoFile
def get_context_data(self, **kwargs):
context = super(VideoFileDetailView, self).get_context_data(**kwargs)
# context['rates'] = VideoRate.objects.filter(video=11, user=1)
return context
Here pk is the id of a video, I need to get the rates of the selected video by the current user.
It would have been useful to show the models. But I think you need to override get(), not get_context_data, as unfortunately the latter doesn't get passed the request, which you need in order to get the user. So:
def get(self, request, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
context['rates'] = VideoRate.objects.filter(video=self.object, user=request.user)
return self.render_to_response(context)
The request should be accessible at self.request. self.request is set at the beginning of the request (in View.dispatch) and should be available any of the subclass methods.
class VideoFileDetailView(DetailView):
model = VideoFile
def get_context_data(self, **kwargs):
context = super(VideoFileDetailView, self).get_context_data(**kwargs)
context['rates'] = VideoRate.objects.filter(video=11, self.request.user)
# note that the object is available via self.object or kwargs.get("object")
return context