adding detail information to django listview object in template - django

I have a listview in which I'm hoping to insert additional details about the object (activity duration and average power) in the same row as the link to the object detail (the best way to describe it would be that I want some detailview attributes inserted into the listview). At the moment, the best I can achieve is a separate context dictionary listed below the object_list, as shown in this screen shot:
And the following is my listview:
class RideDataListView(LoginRequiredMixin, ListView):
model = RideData
context_object_name='object_list'
template_name='PMC/ridedata_list.html'
def get_queryset(self):
queryset = super(RideDataListView, self).get_queryset()
return queryset
def get_context_data(self, *args, **kwargs):
model = RideData
context = super(RideDataListView, self).get_context_data(*args, **kwargs)
records = list(RideData.objects.all().values())
actdict2={}
id=[]
ap=[]
actdur=[]
for record in records:
actdf=pd.DataFrame.from_dict(record)
id.append(actdf['id'].iloc[0])
ap.append(actdf['watts'].mean())
actdur.append(str(timedelta(seconds=len(actdf['time']))))
actdf2=pd.DataFrame()
actdf2['id']=id
actdf2['ap']=ap
actdf2['actdur']=actdur
actdict2=actdf2.to_dict('records')
context['actdict']=actdict2
context['actdur']=actdur
return context
What I haven't been able to nail down in my research is if there is a way to either a) annotate the queryset with stuff from context or b) loop through the context dictionary 'actdict' within the object_list loop (doesn't seem possible based on some attempts) or c) include individual lists (ap and actdur as additions to to query. Just curious for some additional leads to add some more object detail to the basic listview.

Your context is intended to contain your data, but the way it is displayed rely on the HTML template you will use : https://docs.djangoproject.com/en/3.0/topics/templates/

The actual solution to this was to add to queryset object within def get_queryset
def get_queryset(self):
queryset = super(RideDataListView, self).get_queryset()
for obj in queryset:
record=list(obj.watts)
actdf=pd.DataFrame()
actdf['watts']=record
obj.actdur=str(timedelta(seconds=len(actdf['watts'])))
obj.ap=actdf['watts'].mean()
return queryset
This returned the additional summary information I wanted to include in the listview that is also used in detailview

Related

Adding get_context_data method to a class based view breaks django-tables2

I have a class-based view that I use to obtain a queryset and pass to django-tables2 which renders the result. That aspect all works fine. I am trying to pass a record instance from a different queryset to the template, so I can display information above the table django-tables2 produces.
Upon searching, it seems the 'right' way to do so is via the get_context_data method. However when I attempt do add this method to my view, simply obtaining the queryset and returning it, it produces an error Expected table or queryset, not str. I isolated this to being due to {% render_table table %} in my template. Without that, I can access my 'team' object as intended.
Why is this happening? The qs queryset was being passed fine to django-tables2 before I added my get_context_data method. Does the qs queryset have to be returned via get_context_data as well? If so, why?
This is my attempt:
class myteam(LoginRequiredMixin, SingleTableView):
def get_queryset(self):
qs = Contestant.objects.filter(assigned_team=self.request.user.contestant.assigned_team)
qs = qs.exclude(id=self.request.user.contestant.id)
return qs
def get_template_names(self):
return 'xgames/viewteam.html'
def get_table_class(self):
return TeamsTable
def get_context_data(self):
team = Team.objects.get(id=self.request.user.contestant.assigned_team.id)
return {"team": team}
seems like you forgot to call the super() method
class myteam(LoginRequiredMixin, SingleTableView):
# Rest of the code
def get_context_data(self):
context = super().get_context_data()
context["team"] = Team.objects.get(
id=self.request.user.contestant.assigned_team.id
)
return context

Django CBV ListView, accessing both paginated and unpaginated results

I built a list view using generic view class ListView with pagination and search functionality. Now I want to include in the same page a map with markers for all the results, without pagination.
Is there a way to reach both paginated and unpaginated results without having to do a duplicate query?
We can do it by override the method def get_context_data(self, **kwargs). It takes only a single query.
class MyListview(ListView):
def get_context_data(self, **kwargs):
kwargs['obj_list'] = list(kwargs['obj_list'])
my_obj_list = kwargs['obj_list']
context = super(MyListview, self).get_context_data(**kwargs)
context['my_obj_list'] = my_obj_list
return context

Django generic ListView: duplicate queryset in the context

I'm using the generic ListView of Django (1.9.1). I customized the name of the queryset (I called it content_list) to be put in the context. But surprisingly when I look at the context content, I can see object_list along with content_list. If the list is very big this is not very optimised. How can I get rid of object_list?. Here is my view:
class Home(ListView): #TemplateView
context_object_name = 'content_list'
template_name = 'website/index.html'
paginate_by = CONTENT_PAGINATE_BY
def get_queryset(self):
cc_id = self.kwargs.get('cc_id')
if cc_id != None:
qs = Content.objects.filter(category=cc_id)
else:
qs = Content.objects.all()
return qs.order_by('-created_on')
def get_context_data(self, **kwargs):
context = super(Home, self).get_context_data(**kwargs)
context['content_category_list'] = ContentCategory.objects.all()
print(context)
return context
I'm pretty sure they're both reference to the same list in memory.
From the docs:
Well, if you’re dealing with a model object, this is already done for you. When you are dealing with an object or queryset, Django is able to populate the context using the lower cased version of the model class’ name. This is provided in addition to the default object_list entry, but contains exactly the same data, i.e. publisher_list.
Aside from that, even if they weren't referencing the same data, you're forgetting that querysets are executed lazily so if you never use the other list then it is never executed.
This is by design. It's not another interaction to the database, but a second reference.

Django multiple forms with modelchoicefield -> too many queries

I have a table of forms of the same class which contains ModelChoiceField. And each form in one row has the same queryset for this field. Problem is that every time the form is rendered, it is a new query which increases unbearably the number of queries.
The only solution I came up with is to construct the form on the go with js instead of letting django to render it itself. Is there a way to cache these querysets or somewhat preload it at once?
views.py:
shift_table=[]
for project in calendar_projects:
shift_table.append([])
project_branches = project.branches.all()
for i, week in enumerate(month):
for day in week:
shift_table[-1].append(
CreateShiftCalendarForm(initial={'date': day}, branch_choices=project_branches))
forms.py:
CreateShiftCalendarForm(EditShiftCalendarForm):
class Meta(ShiftForm.Meta):
fields = ('project_branch', 'date') + ShiftForm.Meta.fields
widgets = {'date': forms.HiddenInput(), 'length': forms.NumberInput(attrs={'step': 'any'}), 'project_branch': forms.Select()}
def __init__(self, *args, **kwargs):
branch_choices = kwargs.pop('branch_choices', ProjectBranch.objects.none())
super(CreateShiftCalendarForm, self).__init__(*args, **kwargs)
self.fields['project_branch'].queryset = branch_choices
self.fields['project_branch'].empty_label = None
ModelChoiceField is an subclass of ChoiceField in which "normal" choices are replaced with iterator that will iterate through provided queryset. Also there is customized 'to_python' method that will return actual object instead of it's pk. Unfortunately that iterator will reset queryset and hit database once again for each choice field, even if they are sharing queryset
What you need to do is subclass ChoiceField and mimic behaviour of ModelChoiceField with one difference: it will take static choices list instead of queryset. That choices list you will build in your view once for all fields (or forms).
A maybe less invasive hack, using an overload of Django's FormSets and keeping the base form untouched (i.e. keeping the ModelChoiceFields with their dynamic queryset):
from django import forms
class OptimFormSet( forms.BaseFormSet ):
"""
FormSet with minimized number of SQL queries for ModelChoiceFields
"""
def __init__( self, *args, modelchoicefields_qs=None, **kwargs ):
"""
Overload the ModelChoiceField querysets by a common queryset per
field, with dummy .all() and .iterator() methods to avoid multiple
queries when filling the (repeated) choices fields.
Parameters
----------
modelchoicefields_qs : dict
Dictionary of modelchoicefield querysets. If ``None``, the
modelchoicefields are identified internally
"""
# Init the formset
super( OptimFormSet, self ).__init__( *args, **kwargs )
if modelchoicefields_qs is None and len( self.forms ) > 0:
# Store querysets of modelchoicefields
modelchoicefields_qs = {}
first_form = self.forms[0]
for key in first_form.fields:
if isinstance( first_form.fields[key], forms.ModelChoiceField ):
modelchoicefields_qs[key] = first_form.fields[key].queryset
# Django calls .queryset.all() before iterating over the queried objects
# to render the select boxes. This clones the querysets and multiplies
# the queries for nothing.
# Hence, overload the querysets' .all() method to avoid cloning querysets
# in ModelChoiceField. Simply return the queryset itself with a lambda function.
# Django also calls .queryset.iterator() as an optimization which
# doesn't make sense for formsets. Hence, overload .iterator as well.
if modelchoicefields_qs:
for qs in modelchoicefields_qs.values():
qs.all = lambda local_qs=qs: local_qs # use a default value of qs to pass from late to immediate binding (so that the last qs is not used for all lambda's)
qs.iterator = qs.all
# Apply the common (non-cloning) querysets to all the forms
for form in self.forms:
for key in modelchoicefields_qs:
form.fields[key].queryset = modelchoicefields_qs[key]
In your view, you then call:
formset_class = forms.formset_factory( form=MyBaseForm, formset=OptimFormSet )
formset = formset_class()
And then render your template with the formset as described in Django's doc.
Note that on form validation, you will still have 1 query per ModelChoiceField instance, but limited to a single primary key value each time. That is also the case with the accepted answer. To avoid that, the to_python method should use the existing queryset, which would make the hack even hackier.
This works at least for Django 1.11.
I subclassed ChoiceField as suggested by GwynBleidD and it works sufficiently for now.
class ListModelChoiceField(forms.ChoiceField):
"""
special field using list instead of queryset as choices
"""
def __init__(self, model, *args, **kwargs):
self.model = model
super(ListModelChoiceField, self).__init__(*args, **kwargs)
def to_python(self, value):
if value in self.empty_values:
return None
try:
value = self.model.objects.get(id=value)
except self.model.DoesNotExist:
raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')
return value
def valid_value(self, value):
"Check to see if the provided value is a valid choice"
if any(value.id == int(choice[0]) for choice in self.choices):
return True
return False

Adding an extra variable to a column in django-tables2

Trying to build a Django (1.4) site that has some pages that can be either loaded in a popup or not. Some of these pages contain a listview, implemented in Django-tables2
When a page is loaded as a popup, an extra URL parameter is added; for example
/backoffice/popup/articlegroups/ is the same page as /backoffice/articlegroups/ , but shown as a popup.
My question is how to add this extra piece of info (popup or not) to the LinkColumns in Django-tables2, because the links to the edit-page, also need to have this information.
Django-tables2 has an accessor, which can be used to access properties in the queryset, but I need to add an extra piece of data, outside of the queryset. I have seen that adding extra data to an existing dataset is tricky at best, also, that doesn't feel very clean.
I was wondering if there isn't a simple way to add extra data to the tables or columns class, I have tried looking in the table.meta class as well, but to no avail.
My code is as follows:
TABLES.PY
class ArticlegroupTable(tables.Table):
artg_name = LinkIfAuthorizedColumn(
'ArticlegroupUpdate',
args=["popup", A('pk')],
edit_perm="articles.maintenance",
)
This ofcourse works, but it is adding the "popup" arugument as a fixed string as you can see...
class ArticlegroupTable(tables.Table):
artg_name = LinkIfAuthorizedColumn(
'ArticlegroupUpdate',
args=[A('popup'), A('pk')],
edit_perm="articles.maintenance",
)
This does not work, because there isn't a "popup" property in the queryset...
VIEWS.PY
def get_context_data(self, ** kwargs):
# get context data to be passed to the respective templates
context = super(ArticlegroupSearch, self).get_context_data(**kwargs)
data = self.get_queryset()
table = ArticlegroupTable(data, self.request)
RequestConfig(self.request, paginate={
"per_page": 5,
}).configure(table)
context.update({'table': table})
if 'popup' in self.kwargs:
context.update({'popup': self.kwargs['popup']})
return context
It seems that this is not a very far-fetched scenario (adding a URL parameter to a table/column in tables2), so I was wondering if anyone knows of a simple way to do so.
Thanks,
Erik
If you're after a quick hack, just implement the table's __init__ method and add the popup arg to the LinkColumns dynamically:
class ArticlegroupTable(tables.Table):
def __init__(self, *args, **kwargs):
if kwargs.pop("popup", False):
for column in self.base_columns.values():
if isinstance(column, tables.LinkColumn):
column.args.insert(0, "popup")
super(Table, self).__init__(*args, **kwargs)
# …
Then in your view pass in a popup argument:
def get_context_data(self, ** kwargs):
# get context data to be passed to the respective templates
context = super(ArticlegroupSearch, self).get_context_data(**kwargs)
data = self.get_queryset()
popup = self.kwargs.get('popup')
table = ArticlegroupTable(data, self.request, popup=popup)
RequestConfig(self.request, paginate={
"per_page": 5,
}).configure(table)
context.update({'table': table, 'popup': popup})
return context