Dynamic field names in Django Mixin - django

I have these class based ListView's which I would like to filter by date. I have a simple mixin
to display the filterform, which works great:
class MonthYearFormMixin(object):
def get_context_data(self, **kwargs):
context = super(MonthYearFormMixin, self).get_context_data(**kwargs)
context['monthyearform'] = MonthYearForm(self.request.GET)
return context
I would like to extend the functionality of this mixin to include the queryset filtering, but my models
have different date fields that need to be filtered on, one might be start_date, another might be
invoice_date. Of course someone might say, "rename them all 'date'", but that's not representative
of my models and furthermore, I might have a model with start_date and end_date, but only want to filter
on start_date. Here are my views:
class SentList(MonthYearFormMixin, ListView):
model = Sent
context_object_name = 'object'
template_name = 'sent_list.html'
def get_queryset(self):
qs = self.model.objects.all()
if 'month' in self.request.GET:
if int(self.request.GET['month']) > 0:
qs = qs.filter(start_date__month=self.request.GET['month'])
if 'year' in self.request.GET:
if int(self.request.GET['year']) > 0:
qs = qs.filter(start_date__year=self.request.GET['year'])
return qs
class ReceivedList(MonthYearFormMixin, ListView):
model = Received
context_object_name = 'object'
template_name = 'received_list.html'
def get_queryset(self):
qs = self.model.objects.all()
if 'month' in self.request.GET:
if int(self.request.GET['month']) > 0:
qs = qs.filter(invoice_date__month=self.request.GET['month'])
if 'year' in self.request.GET:
if int(self.request.GET['year']) > 0:
qs = qs.filter(invoice_date__year=self.request.GET['year'])
return qs
The only thing that's different is the name of the date field, so it's frustrating to have to repeat
this code.

Why not create a member variable called date_field_name where you store the name of the field names which should be processed by your QuerySet processor mixin?
This list would be defined in the class which uses the mixin.
Something like
class MonthYearFormMixin(object):
def get_context_data(self, **kwargs):
context = super(MonthYearFormMixin, self).get_context_data(**kwargs)
context['monthyearform'] = MonthYearForm(self.request.GET)
return context
def get_queryset(self):
qs = self.model.objects.all()
if 'month' in self.request.GET:
if int(self.request.GET['month']) > 0:
kwargs = {('%s__month' % self.date_field_name): self.request.GET['month']}
qs = qs.filter(**kwargs)
if 'year' in self.request.GET:
if int(self.request.GET['year']) > 0:
kwargs = {('%s__year' % self.date_field_name): self.request.GET['year']}
qs = qs.filter(**kwargs)
return qs
your views would look like this:
class SentList(MonthYearFormMixin, ListView):
model = Sent
context_object_name = 'object'
template_name = 'sent_list.html'
date_field_name = 'start_date'

Related

How I can use pagination for my Class (DetailView) with get_context_data?

How can I get pagination for the current code? I can't change the DetailView to View or ListView, so in this class I get a filter of products.
class CategoryDetailView(DetailView):
model = Category
queryset = Category.objects.all()
context_object_name = 'category'
template_name = 'category_products.html'
...
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
...
products = Product.objects.filter(id__in=[pf_['product_id'] for pf_ in pf])
context['category_products'] = products
return context
Typically one picks the class-based view that is the most convenient to do the job. You can see your view as a ListView of Products that are related to a certain category. By doing this, you make it more convenient to retrieve the related Products, and less convenient to obtain the relevant Category, but the last one is easier.
We can implement the view as:
from django.shortcuts import get_object_or_404
class CategoryDetailView(ListView):
model = Product
context_object_name = 'category_products'
template_name = 'category_products.html'
paginate_by = 1
# …
def get_queryset(self):
qs = super().get_queryset().filter(
category__slug=self.kwargs['slug']
)
qd = self.request.GET.copy()
qd.pop(self.page_kwarg, None)
if 'search' in self.request.GET:
qs = qs.filter(title__icontains=self.request.GET['search'])
elif qd:
qs = qs.filter(
features__value__in=[v for _, vs in qd.lists() for v in vs]
)
if 'sort' in self.request.GET:
qs = qs.order_by(self.request.GET['sort'])
return qs
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['category'] = get_object_or_404(Category, pk=self.kwargs['pk'], slug=self.kwargs['slug'])
return context
The get_queryset here thus implements the advanced querying you implemented, and we add the category by overriding the get_queryset. This queryset will then automatically get paginated by the logic of the ListView.
I would however advise to simplify the logic, usually advanced filtering only makes it more error prone. Furthermore ordering by an arbitrary parameter introduces a security vulnerability, one could for example try to filter on some_foreignkey__some_secure_field to expose data. You thus might want to validate that the order is in a list of acceptable values.

Pass get_queryset in Context

Need help on passing the returned value from get_queryset to the context for rendering, I am reading the Django documentation on Class-based Views and I keep getting the same error name 'querySet' is not defined, any insight on what I'm doing wrong would be appreciated.
class SearchPropertyListView(ListView):
template_name = "search/search_list.html"
def get_queryset(self):
querySet = Property.objects.all()
city_or_neighborhood = self.request.GET.get('city_or_neighborhood')
category = self.request.GET.get('category')
purpose = self.request.GET.get('purpose')
if city_or_neighborhood != '' and city_or_neighborhood is not None:
querySet = querySet.filter(Q(city__title__icontains=city_or_neighborhood)
| Q(neighborhood__title__icontains=city_or_neighborhood)
).distinct()
elif category != '' and category is not None:
querySet = querySet.filter(category__title=category)
elif purpose != '' and purpose is not None:
querySet = querySet.filter(purpose__title=purpose)
return querySet
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(**kwargs)
city = City.objects.all().annotate(
num_property=Count("property")).order_by("-num_property")
categories = Category.objects.all()
purposes = Purpose.objects.all()
featured = list(Property.objects.filter(featured=True))
shuffle(featured)
context['city'] = city
context['categories'] = categories
context['featured'] = featured
context['purposes'] = purposes
context['querySet'] = querySet
return context
In your get_context_data(), you have querySet which is not defined:
def get_context_data(self, *args, **kwargs):
.....
context['purposes'] = purposes
context['querySet'] = querySet <----- Here
return context
So, you should define your querySet like:
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(**kwargs)
city = City.objects.all().annotate(
num_property=Count("property")).order_by("-num_property")
categories = Category.objects.all()
purposes = Purpose.objects.all()
featured = list(Property.objects.filter(featured=True))
querySet = self.get_queryset()
shuffle(featured)
context['city'] = city
context['categories'] = categories
context['featured'] = featured
context['purposes'] = purposes
context['querySet'] = querySet
return context
*Note:- If you are expecting querySet from get_queryset(), then know that these are two different methods and you defined querySet locally. Or, you have to get that from self.get_queryset() as mentioned by #OlegТ.

Combine DetailView and SingleTableView in Django

How can I display a SingleTableView table together with a DetailView in django? What I want is to include or combine both into one. So that when calling DeviceDetailView i receive the device's details and a table of the devices's data (stored also in another model).
I am using django-tables2 for the table.
Someone any idea?
Thank you
class DataListView(SingleTableView):
model = Data
template_name = 'data/list.html'
table_class = DataTable
def post(self, request, *args, **kwargs):
queryset = request.POST
for i in queryset['list'].split(","):
if queryset['action'] == 'delete':
Data.objects.filter(data_id=i).delete()
return HttpResponseRedirect('/data/')
class DeviceDetailView(DetailView):
template_name = 'devices/detail.html'
def get_object(self):
id_ = self.kwargs.get("id")
return get_object_or_404(Device, id=id_)
EDIT
I know it should look something like this:
class DeviceDetailView(SingleTableView, DetailView):
template_name = "devices/device-detail.html"
model = Device
table_class = DeviceTable
def get_context_data(self, **kwargs):
self.object = self.get_object()
context = super(DetailView, self).get_context_data(**kwargs)
id_ = self.kwargs.get("id")
obj = Device.objects.filter(id=id_)
context['object'] = obj
return context
But i think i still don't know how to do this part inside get_context_data so that the elements are merged together...
SOLUTION
Ok it is donde like this:
class DeviceDetailView(SingleTableView, DetailView):
template_name = 'devices/device-detail.html'
model = Data
table_class = DataTable
#crumbs = [('Device detail', 'DeviceDetailView')]
def get_context_data(self, **kwargs):
self.object = self.get_object()
context = super(DetailView, self).get_context_data(**kwargs)
table = self.get_table(**self.get_table_kwargs())
context[self.get_context_table_name(table)] = table
return context
def get_object(self):
id_ = self.kwargs.get("id")
return get_object_or_404(Device, id=id_)
You can serve the table within your DetailsView using custom get_context_data. You can render your table in the template as you usually do with {% render_table table %}.
# import YourModel
# import YourTable
class DeviceDetailView(DetailView):
model = YourModel
template_name = 'devices/detail.html'
def get_context_data(self, **kwargs):
table = YourTable(YourModel.objects.all()) # define your tables data here
context = {"table": table}
return super().get_context_data(**context)

How to make a Django filter display only to some users?

I'm working on a Django project, and want to know how can I make it so that a filter field displays only if the user is a superuser. My filter looks like this:
class OrderFilter(FilterUserMixin):
customer = django_filters.CharFilter(label='Cliente', method='filter_name')
def filter_name(self, queryset, name, value):
queryset = queryset.filter(customer__phone__icontains=value)
return queryset
class Meta:
model = Order
fields = ("store", "customer", "date")
filter_overrides = {
models.CharField: {
'filter_class': django_filters.CharFilter,
'extra': lambda f: {
'lookup_expr': 'icontains',
},
}
}
All normal users should be able to filter by customer and date, but only superusers should be able to filter by store, the rest of users should always see the orders of a single store, so no filtering the store.
I don't know how can I accomplish this, I'm using this view:
class OrderTableView(LoginRequiredMixin, PagedFilteredTableView):
model = Order
table_class = OrderTable
template_name = 'order/order_list.html'
paginate_by = 50
filter_class = OrderFilter
formhelper_class = OrderFilterFormHelper
exclude_columns = ('actions',)
export_name = 'orders'
def get_queryset(self, **kwargs):
qs = super(OrderTableView, self).get_queryset()
if self.request.user.profile.store is not None and self.request.user.is_superuser is False:
qs = qs.filter(store=self.request.user.profile.store)
qs = qs.filter(total__gt=0).filter(address__isnull=False).filter(location__isnull=False)
return qs
one of the ways to solve it:
class OrderFilter(FilterUserMixin):
# omit the other codes
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.request.user.is_superuser and self.filters.get('store'):
self.filters.pop('store')
then drop the related code for the superuser.

objects filter on base of url request

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.