In Django 1.6, I am using a ListView class. This works without paginate_by, but not with. I get object of type 'NoneType' has no len() when I use paginate_by. This is only after the first page, the first page has no issues. I'm not sure why def get_queryset(self) is messing up paginate_by because all it's doing is defining the query_set.
class ViewLog(LoginRequiredMixin, ListView):
template_name = "bot_data/log_view.html"
model = Log
paginate_by = 3
def get_queryset(self):
parameter = self.request.GET.get('search')
user_alpha = self.request.GET.get('user_alpha')
if parameter == "latest":
return Log.most_recent.all()
elif parameter == "oldest":
return Log.least_recent.all()
elif parameter == "ascending":
return Log.user_aplha_ascend.all()
elif parameter == "descending":
return Log.user_aplha_descend.all()
def get_context_data(self, **kwargs):
parameter = self.request.GET.get('search')
context = super(ViewLog, self).get_context_data(**kwargs)
context.update({'current_url': parameter})
return context
EDIT:
I found if I manually append search=ascending as ether /log_view?search=ascending&page=2 or /log_view?page=2&search=ascending in the url window of the browser...both of these combos work. I guess at this point I need to found out how to do the pagination manually and to construct the URL's
Related
I'm trying to elaborate a smart way to switch between a paginated template and a non-paginated one.
I already have a working paginator and I was thinking of adding a button next to it that read "Show all results" that linked to a non-paginated list, from there then there would be another button to go back to the paginated list.
1) Easy Solution
Use 2 ListViews with different assignations of the attribute paginate_by (django default to set pagination), but since I have many lists in my project it wouldn't be convenient (not much smart either).
2) Solution I'm stuck on
Write a Mixin (that will later be extended by my ListViews) to set the variable paginate_by based on a condition and then add some useful variables to the context :
class PaginationMixin:
no_pagination = False
no_pagination_url = ''
def get_paginate_by(self, queryset):
# overwrite django method
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_no_pagination_url(self):
return self.no_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['no_pagination'] = self.no_pagination
context['no_pagination_url'] = self.get_no_pagination_url()
return context
class MyListView(PaginationMixin, ListView):
#...
def get_no_pagination_url(self):
return reverse('mylist_urlname')
PROBLEM: I don't know how to set the no_pagination variable from the template. Is there some way to do this?
Thanks for the help.
UPDATED SOLUTION (edited from #hi-lan solution):
This way it will show all results and also keep urlparams (from filters or other) if present.
class PaginationMixin:
toggle_pagination = False
toggle_pagination_url = ''
no_pagination = False
view_name = ''
urlparams_dict = {}
def get(self, request, page=None, *args, **kwargs):
#store current GET params and pop 'page' key
self.urlparams_dict = request.GET
self.urlparams_dict.pop('page', None)
page = page or request.GET.get('page', '1')
if page == 'all':
page = self.paginate_by = None
self.no_pagination = True
return super().get(request, page=page, *args, **kwargs)
def get_paginate_by(self, queryset):
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_toggle_pagination_url(self):
# variables to set in view to toggle this mixin
if self.toggle_pagination and self.view_name:
if not self.no_pagination:
extra = {'page': 'all'}
self.urlparams_dict.update(extra)
else:
self.urlparams_dict.pop('page', None)
# url keeps track of urlparams adds page=all if toggled
self.toggle_pagination_url = reverse(self.view_name) + '?' + urlencode(self.urlparams_dict)
return self.toggle_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['toggle_pagination_url'] = self.get_toggle_pagination_url()
context['toggle_pagination'] = self.toggle_pagination
return context
The problem is at data flow back from user to indicate non pagination. The only way I can think of is to use special page number. There are two options, depends on which way you config urls.py.
In case of path('objects/page<int:page>/',
PaginatedView.as_view()), special number is 0 (as normal page number
is started from 1).
In case of /objects/?page=3, special number can be all.
In either case, we need to override get method as it is where we can retrieve user's selection.
class PaginationMixin:
no_pagination = False
view_name = ''
def get(self, request, page=None, *args, **kwargs):
page = page or request.GET.get('page', '1')
if page in ['0', 'all']:
page = self.paginate_by = None
else: pass
return super().get(request, page=page, *args, **kwargs)
def get_paginate_by(self, queryset):
# overwrite django method
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_no_pagination_url(self):
# For using path
extra = {'page': '0'}
no_pagination_url = reverse(self.view_name, kwargs=extra)
# For using query params
extra = {'page': 'all'}
no_pagination_url = reverse(self.view_name) + '?' + urlencode(extra)
return no_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['no_pagination'] = self.no_pagination
context['no_pagination_url'] = self.get_no_pagination_url()
return context
class MyListView(PaginationMixin, ListView):
view_name = 'mylist_urlname'
#...
I'm trying the UPDATED SOLUTION of gccallie with this view :
class StageTempList(PaginationMixin, LoginRequiredMixin, SingleTableMixin, FilterView):
view_name = 'stagetemp-list'
table_class = StageTempTable
model = StageTemp
filterset_class = StageTempFilter
template_name = 'stage/stagetemp_list.html'
paginate_by = 30
strict = False
But when get_paginate_by return None, I get 25 rows.
Django version 2.1.2
UPDATE : the PaginationMixin Class I use
class PaginationMixin:
no_pagination = False
view_name = ''
def get(self, request, page=None, *args, **kwargs):
page = page or request.GET.get('page', '1')
if page in ['0', 'all']:
page = self.paginate_by = None
self.no_pagination = True
else: pass
return super().get(request, page=page, *args, **kwargs)
def get_paginate_by(self, queryset):
# overwrite django method
if self.no_pagination:
return None
else:
return super().get_paginate_by(queryset)
def get_no_pagination_url(self):
extra = {'page': 'all'}
no_pagination_url = reverse(self.view_name) + '?' + urlencode(extra)
return no_pagination_url
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['no_pagination'] = self.no_pagination
context['no_pagination_url'] = self.get_no_pagination_url()
return context
I have a Django form that lets users choose a tag from multiple tag options. The problem I am facing is that even when the tag list gets updated, the model form does not get the updated tag list from database. As a result, new tags do not appear in options.
Here is my code in forms.py:
class EnglishTagForm(forms.Form):
tag_choices = [(x.tagName, x.tagName.upper()) for x in ClassTag.objects.filter(
agentId=Agent.objects.get(name='English Chowdhury'))]
tag = forms.CharField(widget=forms.Select(choices=tag_choices,
attrs={'class':'form-control'}))
def __init__(self, *args, **kwargs):
super(EnglishTagForm, self).__init__(*args, **kwargs)
self.fields['tag'].choices = [(x.tagName,
x.tagName.upper()) for x in ClassTag.objects.filter(
agentId=Agent.objects.get(name='English Chowdhury'))]
This form is being instantiated in view. My question is what changes should I do so that tag_choices gets updated from database on every instantiation.
How the above form is used in views.py:
```
def complaintDetail(request, complaint_id):
complaint = Complaints.objects.filter(pk=complaint_id).first()
context = {}
if request.method == 'POST':
agent = Agent.objects.get(name="English Chowdhury")
if "SubmitTag" in request.POST:
englishForm = EnglishTagForm(request.POST)
if englishForm.is_valid:
// Complaint Delete Logic
return redirect('chatbot:modComplaints')
else:
englishForm = EnglishTagForm()
context['eForm'] = englishForm
elif "SubmitBundle" in request.POST:
newTagForm = NewTagForm(request.POST)
if newTagForm.is_valid():
// Complaint Delete Logic
complaint.delete()
return redirect('chatbot:modComplaints')
else:
newTagForm = NewTagForm()
context['newForm'] = newTagForm
else:
englishForm = EnglishTagForm()
context['eForm'] = englishForm
newTagForm = NewTagForm()
context['newForm'] = newTagForm
context['complaint'] = complaint
return render(request, 'chatbot/complaintDetail.html', context)
```
Edit: (For future reference)
I decided to modify the tag attribute and convert CharField to ModelChoiceField, which seems to fix the issue.
Updated Class:
class EnglishTagForm(forms.Form):
tag = forms.ModelChoiceField(queryset=ClassTag.objects.filter(
agentId=Agent.objects.get(name='English Chowdhury')),
empty_label=None, widget=forms.Select(
attrs={'class':'form-control'}))
Please remove the list comprehension from Line 2. So that,
tag_choices = [(x.tagName, x.tagName.upper()) for x in ClassTag.objects.filter(
agentId=Agent.objects.get(name='English Chowdhury'))]
becomes
tag_choices = []
I'm trying to pass a molfile as a string to my template that is being generated from a SMILES string so that I can display a chemical structure.
class CompoundView(DetailView):
template_name = 'CompoundDisplay.html'
model = Compound
def get_context_data(self, **kwargs):
context = super(CompoundView, self).get_context_data(**kwargs)
context_object_name = self.get_context_object_name(self.object)
comp_pk = self.kwargs['pk']
if context_object_name:
context[context_object_name] = self.object
comp_id = Compound.objects.get(pk=comp_pk)
cursor = Smile.objects.get(compound_fk=comp_id.compound_ID)
smile = cursor.smiles
m = Chem.MolFromSmiles(smile)
mol = Chem.MolToMolBlock(m)
correct_mol = mol.replace('\n', '\\n')
context['calc_factors'] = cursor
context['mols'] = correct_mol
context.update(kwargs)
return context
Everything is working fine except the mol string, which is getting passed through GET to the page ending up with this in the url, which obviously isn't working. Is there a way in a CBV to load this as POST? Or is there somewhere else I’m going wrong?
[15/Jan/2015 11:20:27] "GET /compounds/compound/298/RDKit%20%20%20%20%20%20%20%20%20%20%2044%2045%20%200%20%200%20%200%20%200%20%200%20%200%20%200%20%200999%20V2000%20%20%20%200.0000%20%20%20%200.0000%20%20%20%200.0000%20O%20%20%200%20%200%20%200%20%200%20%200%20%....2043%20%201%20%200%2043%2044%20%202%20%200%2043%20%202%20%201%20%200%2039%2035%20%201%20%200M%20%20END HTTP/1.1" 404 18005
I am trying to amend my get_success_url so that if any kwargs have been passed to it, I can build the returned url using them.
Heres what I have so far:
class CalcUpdate(SuccessMessageMixin, UpdateView):
model = Calc
template_name = 'calc/cru_template.html'
form_class = CalcForm
def archive_calc(self, object_id):
model_a = Calc.objects.get(id = object_id)
model_b = Calc()
for field in model_a._meta.fields:
setattr(model_b, field.name, getattr(model_a, field.name))
model_b.pk = None
model_b.save()
self.get_success_url(idnumber = model_b.pk)
def form_valid(self, form):
#objects
if self.object.checked == True:
object_id = self.object.id
self.archive_calc(object_id)
#save
def get_success_url(self, **kwargs):
if kwargs != None:
return reverse_lazy('detail', kwargs = {'pk': kwargs['idnumber']})
else:
return reverse_lazy('detail', args = (self.object.id,))
So far this just gives a keyerror detailing 'idnumber'.
I have printed kwargs['idnumber'] and it returns the pk as expected however I just cant seem to see where I am going wrong with this.
Thanks in advance.
form_valid should return a HttpResponseRedirect https://github.com/django/django/blob/master/django/views/generic/edit.py#L57 which in your case, you never do. I dont know if you have any code after #save, but take a look at the comments I made in your code
class CalcUpdate(SuccessMessageMixin, UpdateView):
model = Calc
template_name = 'calc/cru_template.html'
form_class = CalcForm
def archive_calc(self, object_id):
model_a = Calc.objects.get(id = object_id)
model_b = Calc()
for field in model_a._meta.fields:
setattr(model_b, field.name, getattr(model_a, field.name))
model_b.pk = None
model_b.save()
return self.get_success_url(idnumber = model_b.pk) # you never return this value
def form_valid(self, form):
#objects
if self.object.checked == True:
object_id = self.object.id
return HttpResponseRedirect(self.archive_calc(object_id)) # you never return a `HttpResponse`
#save -- If this is where you are saving... you can store the value from archive and return it after saving
def get_success_url(self, **kwargs):
if kwargs != None:
return reverse_lazy('detail', kwargs = {'pk': kwargs['idnumber']})
else:
return reverse_lazy('detail', args = (self.object.id,))
Also you don't need to manually copy the fields, just do (assuming there are no unique constraints because if there were, your version would fail too):
def archive_calc(self, object_id):
c = self.model.objects.get(id = object_id)
c.pk = None
c.save()
return self.get_success_url(idnumber = c.pk)
After playing around with #Ngenator's answer and various other posts on here I have the following working code. However its not very nice to look at :(
def get_success_url(self):
if self.pknumber != None:
return reverse_lazy('pstdetail', args = (self.pknumber,))
else:
return reverse_lazy('pstdetail', args = (self.object.id,))
I have this self.pknumber = model_b.pk in the necessary place within the view and self.pknumber = None else where to enable the if statement to build the required url. Hope this helps anyone and feel free to point out any errors/improvements.
https://ccbv.co.uk/projects/Django/4.0/django.views.generic.edit/UpdateView/
You cannot pass parameters in get_success_url(self) method. You can only refer to self parameter. For example self.kwargs['pk'].
I am getting the above error when I have tried to convert my inline_formset to have at least the first row required. (please see here for the StackOverflow question)
My existing code is below:
#views.py
def application(request, job_id):
job = get_object_or_404(Job, pk=job_id)
#return 404 if job isn't yet published
if (job.pub_date>timezone.now() or job.close_date<timezone.now()):
return HttpResponseNotFound('<h1>Job not found</h1>')
#create all the inlineformsets (can_delete) set to false as will always be empty upon population
EducationInlineFormSet = inlineformset_factory(Applicant, Education, extra=1, can_delete=False)
QualificationInlineFormSet = inlineformset_factory(Applicant, Qualification, extra=1, can_delete=False)
EmploymentInlineFormSet = inlineformset_factory(Applicant, Employment, extra=1, can_delete=False)
if request.method == 'POST':
applicant = Applicant(job=job)
form = ApplicantForm(request.POST, instance=applicant)
bottom_form = ApplicantFormBottom(request.POST, instance=applicant)
education_formset = EducationInlineFormSet(request.POST, instance=applicant)
qualification_formset = QualificationInlineFormSet(request.POST, instance=applicant)
employment_formset = EmploymentInlineFormSet(request.POST, instance=applicant)
#check all of the forms and formsets are valid
if form.is_valid() and bottom_form.is_valid() and education_formset.is_valid() and qualification_formset.is_valid() and employment_formset.is_valid():
# save the model to database, directly from the form:
form.save()
bottom_form.save()
education_formset.save()
qualification_formset.save()
employment_formset.save()
return render(request, 'jobs/success.html')
else:
applicant = Applicant(job=job)
form = ApplicantForm(instance=applicant)
bottom_form = ApplicantFormBottom(instance=applicant)
education_formset = EducationInlineFormSet(instance=applicant)
qualification_formset = QualificationInlineFormSet(instance=applicant)
employment_formset = EmploymentInlineFormSet(instance=applicant)
c = {
'job' : job,
'form' : form ,
'bottom_form' : bottom_form,
'education_formset' : education_formset,
'qualification_formset' : qualification_formset,
'employment_formset' : employment_formset,
}
return render(request, 'jobs/application.html', c)
In order to customise the formset I defined the following:
class BaseFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(BaseFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
and pass use it as follows:
EducationInlineFormSet = inlineformset_factory(Applicant, Education, extra=1, can_delete=False, formset=BaseFormSet)
This returns the above error and having read around a lot I'm still none the wiser how I can keep passing an instance to the formset.
Any help would be greatly appreciated.
Regards,
Chris.
I had a similar issue - the problem was the customised formset.
Try subclassing from BaseInlineFormSet (not BaseModelFormSet).
Here is the relevant section of the docs.