Django success url using kwargs - python

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'].

Related

How can I pass a model id from the url to a class based view?

I have a class-based view:
class Create(View):
note_id = None
http_method_names = ['post', 'patch']
default_title = "You fool! This was left empty"
default_body = "Why did you leave this blank :("
def dispatch(self, *args, **kwargs):
method = self.request.POST.get('_method', '').lower()
print('method = ', method)
if method == 'patch':
return self.patch(*args, **kwargs)
elif method == 'post':
self.post(*args, **kwargs)
return super(Create, self).dispatch(*args, **kwargs)
def post(self, note_id):
date = datetime.date.today()
title = self.request.POST.get('title', '')
body = self.request.POST.get('note', '')
# check for blank attributes
if title == "":
title = Create.default_title
if body == "":
body = Create.default_body
note = Note(note_title=title, note_body=body, publish_date=date, edit_date=None)
note.save()
return HttpResponseRedirect(reverse('notes:note'))
def patch(self, note_id):
note = Note.objects.get(id=note_id)
title = self.request.POST.get('title', '')
body = self.request.POST.get('note', '')
# if something changed
if title != note.note_title or body != note.note_body:
# check for blank attributes
if title == "":
title = Create.default_title
if body == "":
body = Create.default_body
note.note_title = title
note.note_body = body
note.edit_date = datetime.date.today()
note.save()
return HttpResponseRedirect(reverse('notes:note'))
and in url.py I have
urlpatterns = [
path('<int:note_id>/create/', views.Create.as_view(), name='create'),
path('<int:note_id>/edit/', views.Create.as_view(), name='edit')
]
Previously, with function-based views the note_id would just be passed to the function automatically. I can not figure out the equivalent of this in class based views. I've tried explictiely passing note_id to each function, but that did not work. I tried included Create.as_view(note_id=note_id) in my url.py, but to no avail.
Currently, running this code I get:
post() got multiple values for argument 'note_id'
As mentioned in the comment above, you need to access these values through self.kwargs. e.g:
class Create(View):
note_id = None # Delete this line
...
# delete the dispatch method - no need to overwrite this.
# Don't include note_id as an argument, but request should be an argument
def post(self, request):
note_id = self.kwargs['note_id']
....
def put(self, request): # Likewise here
note_id = self.kwargs['note_id']
....
It doesn't look like you need to write a custom dispatch method. If all you are doing is calling the appropriate method, then the dispatch method that comes for free with View is just fine :)

Django Pagination: switch between paginated/non-paginated ListView

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

Django form field update

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 = []

some troubles with CreateView in the Django

class Biochemical_analysis_of_blood(CreateView):
model = BiochemicalAnalysisOfBlood
form_class = BiochemicalAnalysisOfBloodForm
template_name = "biochemical_analysis_of_blood.html"
success_url = reverse_lazy("patients")
def get_context_data(self, **kwargs):
context = super(Biochemical_analysis_of_blood, self).get_context_data(**kwargs)
patient = Patient.objects.get(id=1)
context["patient"] = patient
return context
def post(self, request, *args, **kwargs):
analysis = Analyzes()
sid = transaction.savepoint()
analysis.name = request.POST["name"]
analysis.patient_id = Patient.objects.get(id=1)
analysis.who_send = request.POST["who_send"]
analysis.who_is_doctor = request.POST["who_is_doctor"]
analysis.lab_user_id = Doctor.objects.get(id=request.POST["lab_user_id"])
analysis.additional_lab_user = request.POST["lab_user_add"]
analysis.date = '2017-06-18'
analysis.type = 3
analysis.date_analysis = '2017-06-18'
analysis.save()
return super(Biochemical_analysis_of_blood, self).post(request, *args, **kwargs)
I have next algorithm:
Render BiochemicalAnalysisOfBloodForm to the user
When he fills fields and presses button "save" I create a new instance of Analyzes() and fill it programmatically and when in the post method I call super().post() then users data will be written to the model BiochemicalAnalysisOfBlood automatically? But I have next error:
NOT NULL constraint failed:
laboratory_biochemicalanalysisofblood.analysis_id
How can I in hand mode add to the model to the field "analysis" the early created instance of Analyzes()? I don't understand this class to the end where I can find information about all it's opportunities
The main part of your algorithm should reside in your form, because you want to pass the analysis_id to the instance being saved
class BiochemicalAnalysisOfBloodForm(ModelForm):
def save(self, commit=True):
analysis = Analyzes()
sid = transaction.savepoint()
analysis.name = self.data["name"]
analysis.patient_id = Patient.objects.get(id=1)
analysis.who_send = self.data["who_send"]
analysis.who_is_doctor = self.data["who_is_doctor"]
analysis.lab_user_id = Doctor.objects.get(id=self.data["lab_user_id"])
analysis.additional_lab_user = self.data["lab_user_add"]
analysis.date = '2017-06-18'
analysis.type = 3
analysis.date_analysis = '2017-06-18'
analysis.save()
# Your analysis is created, attach it to the form instance object
self.instance.analysis_id = analysis.id
return super().save(commit)
Before doing the super().post you can modify the request.POST data to include your analysis id:
request.POST['analysis_id'] = analysis.id
that might help.
Also note that if the form validation fails in super().post, you will still have created an Analysis object which might not be useful. You could use override the form_invalid method of the CreateView to handle this.

Django ListView get_queryset(self) breaks paginate_by

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

Categories

Resources