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 ?
Related
I'm rewriting my django function view to class based views. I have this current function
#login_required
def settings(request, template_name="settings.html"):
context = {}
context['kcs'] = KlarnaProfile.objects.filter(user_profile__user=request.user)
context['extends'] = ExtendProfile.objects.filter(user_profile__user=request.user)
context['fortnoxs'] = FortnoxProfile.objects.filter(user_profile__user=request.user)
return render(request, template_name, context)
that confirms first if a user is logged in and then get's information linked to that user account
here's what I've got as my class based view
class SettingsView(TemplateView):
template_name = "settings.html"
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
how can I add the three filters that use the logged in user as a filter?
Use get_context_data method like this:
class SettingsView(TemplateView):
...
def get_context_data(self, **kwargs):
context_data = super().get_context_data(**kwargs)
context_data['kcs'] = KlarnaProfile.objects.filter(user_profile__user=self.request.user)
...
return context_data
Neat pick. Instead of doing:
class SettingsView(TemplateView):
...
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
You can do:
#method_decorator(login_required, name='dispatch')
class SettingsView(TemplateView):
...
You might want to read Decorating the class from Django's official documentation.
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 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
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))
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)
...