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
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
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'm trying to filter an object based off of the primary key in my detailed view. Is there a way I can call my primary key in my views.py or some other way I can filter accordingly? Here's my code:
models.py
class Accounts(models.Model):
account_name = models.CharField(max_length=50)
pricing_id = models.ForeignKey('Pricing')
class OrderRecords(models.Model):
order_id = models.ForeignKey('Orders')
account_id = models.ForeignKey('Accounts')
item_id = models.ForeignKey('Items')
views.py
class AccountDetailView(generic.DetailView):
model = Accounts
template_name = "orders/accountdetail.html"
def get_context_data(self, **kwargs):
context = super(AccountDetailView, self).get_context_data(**kwargs)
context['orderrecords'] = OrderRecords.objects.filter(????????)
return context
Update:
So this was the change I made:
views.py
class AccountDetailView(generic.DetailView):
model = Accounts
template_name = "orders/accountdetail.html"
def get_context_data(self, **kwargs):
pk = self.kwargs['pk']
context = super(AccountDetailView, self).get_context_data(**kwargs)
context['orderrecords'] = OrderRecords.objects.filter(account_id=pk)
return context
Yes, in your views, simply call:
def get_context_data(self, **kwargs):
pk = kwargs.get('pk') # this is the primary key from your URL
# your other code
context = super(AccountDetailView, self).get_context_data(**kwargs)
context['orderrecords'] = OrderRecords.objects.filter(????????)
return context
I have two similar classes, query filter is county code DE or NL.
Is possible to make a objects filter on base of url name and keep only one class? For example, if i point my browser to
127.0.0.1:8000/germany
django will call to filter
feed__country__name='DE'
and
127.0.0.1:8000/netherland
will use
feed__country__name='NL'?
My URL:
url(r'^netherland/$', NLFeedList.as_view(), name='nl'),
url(r'^germany/$', DEFeedList.as_view(), name='de'),
VIEWS:
class NLFeedList(PaginationMixin, ListView):
model = FeedItem
template_name = 'nl_feed.html'
context_object_name = 'feed_items'
paginate_by = 20
def get_queryset(self):
items = FeedItem.objects.filter(feed__country__name='NL')
if self.kwargs.get('category', None):
return items.category(self.kwargs.get('category'))
return items
def get_context_data(self, **kwargs):
context = super(NLFeedList, self).get_context_data(**kwargs)
context['categories'] = Category.objects.filter(country__name='NL')
return context
class DEFeedList(PaginationMixin, ListView):
model = FeedItem
template_name = 'de_feed.html'
context_object_name = 'feed_items'
def get_queryset(self):
items = FeedItem.objects.filter(feed__country__name='DE')
if self.kwargs.get('category', None):
return items.category(self.kwargs.get('category'))
return items
def get_context_data(self, **kwargs):
context = super(DEFeedList, self).get_context_data(**kwargs)
context['categories'] = Category.objects.filter(country__name='DE')
return context
You can do something like:
urls.py
url(r'^(?P<country>germany|netherland)/$', FeedList.as_view(), name='feedlist')
and the view:
class FeedList(PaginationMixin, ListView):
model = FeedItem
context_object_name = 'feed_items'
match = {'germany':'DE','netherland':'NL'}
def get_queryset(self):
code = self.match[self.kwargs['country']]
items = FeedItem.objects.filter(feed__country__name=code)
self.template_name = '%s_feed.html' % code.lower()
if self.kwargs.get('category', None):
return items.category(self.kwargs.get('category'))
return items
def get_context_data(self, **kwargs):
context = super(FeedList, self).get_context_data(**kwargs)
context['categories'] = Category.objects.filter(country__name=self.match[self.kwargs['country']])
return context
Also, perhaps you don't need two templates else only one, in this case just remove this line self.template_name = '%s_feed.html' % code.lower() and set the template_name accordingly.
Change your url to
url(r'^(?P<country>netherland|germany)/$', NLFeedList.as_view(), name='nl'),
Then access this new <country> parameter in your view using
country = self.kwargs['country']
Then do the necessary if country = '...': code block in your view.
I'm new to django framework developers, and I have read a lot of documentation of Class-Based View and Forms.
Now, I want to create a single page (for test purpose) that contains a list of cars and a Forms, at the bottom page, for create a new Car.
this is my views.py
class IndexView(ListView):
template_name = "index.html"
context_object_name = "cars"
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context["form"] = CarForm
return context
def get_queryset(self):
self.brand = self.kwargs.pop("brand","")
if self.brand != "":
return Car.objects.filter(brand__iexact = self.brand)
else:
return Car.objects.all()
def post(self, request):
newCar = CarForm(request.POST)
if newCar.is_valid():
newCar.save()
return HttpResponseRedirect("")
else:
return render(request, "index.html", {"form": newCar})
class CarForm(ModelForm):
class Meta:
model = Car
delete = True
and this is a picture with what I want create.
image
My questions are:
1) this is a "Best-Pratice" for this purpose?
2) The {{ car.name.errors }} in my template are always blank (no validation error shows).
Thanks! … and sorry for my english.
You could go other way around. Create a FormView and put the list of cars in context. That way form handling becomes easier. Like this -
class CarForm(ModelForm):
class Meta:
model = Car
delete = True
class IndexView(FormView):
template_name = "index.html"
form_class = CarForm
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
# Pass the list of cars in context so that you can access it in template
context["cars"] = self.get_queryset()
return context
def get_queryset(self):
self.brand = self.kwargs.pop("brand","")
if self.brand != "":
return Car.objects.filter(brand__iexact = self.brand)
else:
return Car.objects.all()
def form_valid(self, form):
# Do what you'd do if form is valid
return super(IndexView, self).form_valid(form)