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
Related
Two of my views share a substantial amount of code, which makes it hard to maintain. For that reason I decided to move the common code into a custom mixin.
A simplistic example:
class StatisticsMixin(object):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user_products = Product.objects.filter(user=self.request.user).select_related()
context["total_products"] = user_products.count()
context["total_products_open"] = user_products.total_new()
return context
Now, my views would look like this:
class DashboardView(LoginRequiredMixin, StatisticsMixin, TemplateView):
model = Product
template_name = "products/dashboard.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user_products = Product.objects.filter(user=self.request.user).select_related()
# Some other code
return context
class AnalyticsView(LoginRequiredMixin, StatisticsMixin, TemplateView):
model = Product
template_name = "products/analytics.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user_products = Product.objects.filter(user=self.request.user).select_related()
# Some other code
return context
The issue is the following in the custom mixin, StatisticsMixin:
user_products = Product.objects.filter(user=self.request.user).select_related()
which in different views could be something else.
So, I am trying to find a way for the custom mixin, to get the data from the view and not be hardcoded in it.
class StatisticsMixin(object):
def get_user_products(self):
return Product.objects.filter(user=self.request.user).select_related()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user_products = self.get_user_products()
context["total_products"] = user_products.count()
context["total_products_open"] = user_products.total_new()
return context
class DashboardView(LoginRequiredMixin, StatisticsMixin, TemplateView):
model = Product
template_name = "products/dashboard.html"
def get_user_products(self):
return what ever you want
class SupervisionView(MyBaseView, TemplateView):
template_name = 'research/a1.html'
def get_context_data(self, **kwargs):
context = super(SupervisionView, self).get_context_data(**kwargs)
context['supervisions'] = list1
return context
def post(self, request, *args, **kwargs):
if 'confirm_supervision1' in request.POST:
return redirect(reverse_lazy('t_app:dept1', kwargs={'year': self.kwargs['year']}))
class SupervisionView2(MyBaseView, TemplateView):
template_name = 'research/a2.html'
def get_context_data(self, **kwargs):
context = super(SupervisionView2, self).get_context_data(**kwargs)
context['supervisions'] = list 2
return context
def post(self, request, *args, **kwargs):
if 'confirm_supervision2' in request.POST:
return redirect(reverse_lazy('t_app:dept2', kwargs={'year': self.kwargs['year']}))
I have some 20 odd functions doing the same thing again. only change is the context variable and redirect url in each view. What is the best way to compress this?
You could make a custom mixin:
class MyDRYMixin(object):
context_variable = None
post_variable = None
redirect_name = None
def get_context_data(self, **kwargs):
context = super(MyDRYMixin, self).get_context_data(**kwargs)
if self.context_variable is not None:
context['supervisions'] = self.context_variable
return context
def post(self, request, *args, **kwargs):
if self.post_variable is not None and self.post_variable in request.POST:
return redirect(reverse_lazy(self.redirect_name, kwargs={'year':self.kwargs['year']}
Then use that mixin in your views, making sure to define those three variables:
class SupervisionView(MyBaseView, MyDRYMixin, TemplateView):
template_name = 'research/a1.html'
context_variable = 'list1'
post_variable = 'confirm_supervision1'
redirect_name = 't_app:dept1'
You can set your variables equal to anything you want. When you mix your MyDRYMixin into a view, the values you provide in that view will be used rather than the values defined in the base mixin class. So in our example above, context_variable == list1. If we hadn't defined a context_variable in SupervisionView it would have defaulted to None, the value set in our base mixin.
If you want context_variable to reference the current user, for instance:
class SupervisionView(MyBaseView, MyDRYMixin, TemplateView):
context_variable = self.request.user
...
(Edit: I made a mistake here! This should be done within get_context_data since we are accessing self.request.user: https://docs.djangoproject.com/en/1.11/ref/class-based-views/mixins-simple/)
Or maybe you have some kind of test you want to apply, like using one context_variable for authenticated users and a different one for unauthenticated users:
class SupervisionView(MyBaseView, MyDRYMixin, TemplateView):
def set_context_variable(self):
if self.request.user.is_authenticated():
self.context_variable = 'foo'
else:
self.context_variable = 'bar'
Or:
class SupervisionView(MyBaseView, MyDRYMixin, TemplateView):
def get_context_variable(self):
if self.user.is_authenticated():
return 'foo'
return 'bar'
context_variable = self.get_context_variable()
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 have two simple class based generic views which simply render a template. Since they look almost the same and I want to stay dry I wanted to ask you, what would be the smartest and best way to implement this right now:
First View
class Foo(TemplateView):
template_name = "Foo.html"
def get_context_data(self, **kwargs):
user = User.objects.get(username = self.request.user)
context = super(Foo, self).get_context_data(**kwargs)
context['foo_objs'] = Foo.objects.filter(user = user.id)
return context
class Bar(TemplateView):
template_name = "bar.html"
def get_context_data(self, **kwargs):
user = User.objects.get(username = self.request.user)
context = super(Bar, self).get_context_data(**kwargs)
context['bar_objs'] = Bar.objects.filter(user = user.id)
return context
Create intermediate view class with additional attribute model and then inherit your views from it:
class ModelListTemplateView(TemplateView):
model = None
def get_template_names(self):
return [self.model._meta.model_name + '.html']
def get_context_data(self, **kwargs):
context = super(ModelListTemplateView, self).get_context_data(**kwargs)
user = User.objects.get(username=self.request.user)
context['%s_objs' % self.model._meta.model_name] = \
self.model.objects.filter(user=user.id)
return context
class FooView(ModelListTemplateView):
model = Foo
class BarView(ModelListTemplateView):
model = Bar
I have to specify a success_url, otherwise I get an error. So how to specify it, in order to stay to the same page?
Also, is everything else correct regarding the SearchView, beucase I have a feeling that something is missing. My context should be composed by form, query, concepts, language and languages.
Thanks
urls.py
url(r'^(?P<langcode>[a-zA-Z-]+)/search/$', SearchView.as_view(), name='search').
views.py
class _LanguageMixin(object):
def dispatch(self, request, *args, **kwargs):
self.langcode = kwargs.pop("langcode")
self.language = get_object_or_404(Language, pk=self.langcode)
return super(_LanguageMixin, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(_LanguageMixin, self).get_context_data(**kwargs)
context.update({"language": self.language,
"languages": Language.objects.values_list('code',
flat=True)})
return context
class SearchView(_LanguageMixin, FormView):
template_name = "search.html"
form_class = SearchForm
success_url = #......
query = ''
concepts = []
def get_initial(self):
return {'langcode': self.langcode}
def get_context_data(self, **kwargs):
context = super(SearchView, self).get_context_data(**kwargs)
context.update({"query": self.query, "concepts": self.concepts})
return context
def form_valid(self, form):
self.query = form.cleaned_data['query']
self.concepts = # here is a long DB query; function(query)
return super(SearchView, self).form_valid(form)
[EDIT]
I did this:
def get_success_url(self):
return reverse('search', kwargs={'langcode': self.langcode})+"?query={}".format(self.query)
The form renders, but whenever I search for anything, I get back the empty search text field. And the URL looks something like this: http://localhost:8000/en-US/search/?query=asd
By default, a FormView (actually, any subclass of ProcessFormView) will return a HttpResponseRedirect in form_valid. As you are calling the super's method in your form_valid method, you also return a HttpResponseRedirect. In the process, the actual POST data is lost, and though you pass it as a GET parameter, it is not used in the actual form.
To fix this, you need to not call super in your form_valid method, but instead return a rendered template in a HttpResponse object, e.g.:
def form_valid(self, form):
self.query = form.cleaned_data['query']
self.concepts = # here is a long DB query; function(query)
return self.render_to_response(self.get_context_data(form=form))