I'll be implementing a customized class based generic view by sub-classing ListView in my views.py. My question is how will be able to access the request (HttpRequest object) parameter in my sub-class? The HttpRequest object that I am pertaining to is the default request parameter for all functions inside views.py. Example:
def search(request):
To be clearer, here's what I've tried so far:
**views.py
class CustomListView(ListView):
temp = ""
def get(self, request, *args, **kwargs):
self.temp = request.GET.get('temp')
return super(CustomListView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(CustomListView, self).get_context_data(**kwargs)
context['temp'] = self.temp
return context
**urls.py
url(r'^temp/$, CustomListView.as_view(queryset=Document.objects.all()[:1],template_name="temp.html")),
**temp.html
{% extends 'base.html' %}
{% block content %}
<h2>{{ temp }}
{% endblock %}
But all I am seeing when I run the server and access /temp/ (temp.html) is 'None'. So meaning, 'temp' is "" or 'temp' was not created at all.
Any ideas are greatly appreciated. Thanks!
In general, you can use self.request in CBV methods that haven't been passed a request.
So you can use
context['temp'] = self.request.GET.get('temp')
in your get_context_data method, and then delete your get override method entirely.
Related
I started working with django and I have come across a problem that I am not able to solve. I believe this should be easy but I cannot figure it out.
I am getting an Id from a template which I pass through a URL.
data.html
...
<div class="container">
<form method="GET" action="{% url 'data:next' %}">
<input type="hidden" required name='job_id'>
<button type="submit" class="btn btn-primary">Run Next</button>
{% csrf_token %}
</form>
</div>
...
url.py
app_name = "data"
urlpatterns = [
...
...
path('next/', RunNextView.as_view(), name='next'),
]
This seems to pass the job_id value to the URL as after selecting a checkbox (in this case with job_id pointing at 44) and clicking on the Run Next button I get to a url such as:
http://localhost:8000/data/next/?job_id=44&Some-token
which I think is good. Now I want to pass the number 44 to a class based view which is the problem and I was not able to do it.
As shown below I need the job_id in order to run the run_next_task task.
How do i grab that 44 and pass it to the form_valid method inside the CBV ?
views.py
class RunNextView(FormView):
template_name = 'next.html'
form_class = RunForm
success_url = reverse_lazy('data:list')
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return redirect(reverse_lazy('account:login'))
else:
return super(RunNextView, self).dispatch(request, *args, **kwargs)
def form_valid(self, form):
cal = form.save(False)
cal.user = self.request.user
cal.save()
cal.send_update()
job_id = self.request.GET.get('job_id') //this way doesn't work
run_next_task.delay(pk=cal.pk, dir=job_id)
return redirect(self.success_url)
def form_invalid(self, form):
for field in form.errors:
form[field].field.widget.attrs['class'] += F" {form.error_css_class}"
return self.render_to_response(self.get_context_data(form=form))
I did countless experiments.
job_id = self.request.GET.get('job_id') inside the form_valid and pretty much every other combination. I tried using session and global variables. I tried using kwargs in many different ways such as job_id=self.kwargs['job_id'].
Non of them worked. There must be something basic and fundamental that I am missing.
Any help is appreciated.
Thank you ;]
If you want to send data with a form, you have to choose POST method not GET. add the "job_id" (or job.id. what ever u passed) to the value of the input as follow:
<form method="POST" action="{% url 'data:next' %}">
<input type="hidden" value="{{job_id}}" required name='job_id'>
<button type="submit" class="btn btn-primary">Run Next</button>
{% csrf_token %}
</form>
then in view, define a post method:
class RunNextView(FormView):
def post(self,request):
# Since name='job_id'
posted_id=request.POST["job_id"]
I haven't tried this on my own but just give it a try ;)
I added a new attribute in __init__
Then use it in form_valid.
class RunNextView(FormView):
template_name = 'next.html'
form_class = RunForm
success_url = reverse_lazy('data:list')
# Add this ↓
def __init__(self, **kwargs):
self.job_id = ""
super().__init__(**kwargs) # <- You don't need put 'self' here. So, remove it.
# Add this as well ↓
def get(self, request, *args, **kwargs):
self.job_id = request.GET.get('job_id')
super().get(request, *args, **kwargs)# <- Remove 'self' here
# some code...
def form_valid(self, form):
# some code...
job_id = self.job_id # <- Change here
# some code...
# some code...
EDIT
I am overriding the __init__ function in this example, and I let the parent function whatever it needs to do by doing super().__init__(**kwargs) after I create a new attribute job_id.
You can access self.job_id anywhere inside the class.
I implemented a custom model field:
class CustomCharField(models.CharField):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.my_attr = "MY_ATTR"
Now I want to be able to display that additional attribute when I'm iterating over all model's fields in change_form_template:
{% block field_sets %}
{% for fieldset in adminform %}
{% for line in fieldset %}
{% for field in line %}
{{ field.field }}
{% if field.field.my_attr %}
{{ field.field.my_attr }}
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% endblock %}
Unfortunately my_attr is not visible. I was searching the proper method in the django docs but without any success. I was also trying to use the custom form_class (inheriting from django.forms.fields.CharField)
EDIT:
Ok, so I need both custom form field and model field. Given the following code, what should I change to make it working? (of course I ignore passing the attribute value in that example). Why my_attr from CustomFormField is still not accessible from the template?
class CustomFormField(fields.CharField):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.my_attr = "MY_ATTR2"
class CustomCharField(models.CharField):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.my_attr = "MY_ATTR"
def formfield(self, **kwargs):
defaults = {'form_class': CustomFormField}
defaults.update(kwargs)
return super().formfield(**defaults)
Ok, thanks to the advices from #AbdulAzizBarkat and #Guillaume I finally managed how to resolve the problem.
In order to expose attribute of the model field at the HTML template, it's needed to implement also custom form field and pass the attribute in default dict in formfield method:
from django.db import models
from django.forms import fields
class CustomFormField(fields.CharField):
def __init__(self, my_attr, *args, **kwargs):
super().__init__(*args, **kwargs)
self.my_attr = my_attr
class CustomCharField(models.CharField):
def __init__(self, my_attr, *args, **kwargs):
super().__init__(*args, **kwargs)
self.my_attr = my_attr
def formfield(self, **kwargs):
defaults = {'form_class': CustomFormField, "my_attr": self.my_attr}
defaults.update(kwargs)
return super().formfield(**defaults)
Another change is the way of accessing to the attribute in the HTML template specified by setting change_form_template value. In that case it should be {{ field.field.field.my_attr }}
I have this view using generic in django and I need to manipulate it. But it seems that whenever I tried using setattr it doesn't manipulate/replace the data in the queryset.
view.py
class ArticleListView(ListView): #using the Generic ListView in django
template_name = 'article/article_list_view.html'
queryset = Article.objects.all()[:5]
def get(self, request, *args, **kwargs):
setattr(self.queryset[0], 'title', 'something') #Trying to change the title in the first element
print(getattr(self.queryset[0], 'title')) #it outputs the old value
return super().get(request, *args, **kwargs)
article_list_view.html
{% extends 'base.html' %}
{% block content %}
{% for instance in object_list %}
<h5>{{instance.title}}</h5>
{% autoescape off %}
<p>{{instance.content| capfirst}}</p>
{% endautoescape %}
{% endfor %}
{% endblock content %}
Do you have any idea to solve this issue? Or maybe because it was immutable?
This is a challenge because according to Django's docs on querysets "a QuerySet can be constructed, filtered, sliced, and generally passed around without actually hitting the database". The first thing you and I both tried was grabbing the first object out of the queryset and setting the attribute. This doesn't help because the queryset itself isn't being evaluated until you iterate over it: {% for instance in object_list %}. When that happens the model instance will be retrieved from the database again.
The best solution I can see is to force the queryset to be evaluated before updating the model instance.
class ArticleListView(ListView):
template_name = 'article/article_list_view.html'
queryset = Article.objects.all()[:5]
def get_context_data(self, *, object_list=None, **kwargs):
# Overwriting this method to receive exactly the same arguments as the original
# https://github.com/django/django/blob/3.0.6/django/views/generic/list.py#L113
if object_list is None:
object_list = self.object_list
if not isinstance(object_list, tuple): # Force the queryset into a tuple so it's evaluated
object_list = tuple(object_list)
object_list[0].title = 'something' # Update the instance's title
return super().get_context_data(object_list=object_list, **kwargs)
This does have the downside of article_list won't be available in the context unless you explicitly set context_object_name = 'article_list' on your view, but you already weren't using that variable.
I've tested out parts of this code to verify that my theory is right, but I haven't run the code in it's entirety, so you might need to tweak things to get it working exactly as you want it.
I'm able to insert (lame) static text onto the change form admin page, but I'd really like it to use the context of the current object being edited!
For instance, I want to format on the MyObject change form a URL to include the ID from a ForeignKey connected object (obj) as a link.
My admin objects:
class MyObjectChangeForm(forms.ModelForm):
class Meta:
model = MyObject
fields = ('field1', 'obj',)
class MyObjectAdmin(admin.ModelAdmin):
form = MyObjectChangeForm
list_display = ('field1', 'obj')
def render_change_form(self, request, context, *args, **kwargs):
self.change_form_template = 'admin/my_change_form.html'
extra = {'lame_static_text': "something static",}
context.update(extra)
return super(MyObjectAdmin, self).render_change_form(request,
context, *args, **kwargs)
My template templates/admin/my_change_form.html:
{% extends "admin/change_form.html" %}
{% block form_top %}
{{ lame_static_text }}
<a href="http://example.com/abc/{{ adminform.data.obj.id }}?"/>View Website</a>
{% endblock %}
The {{adminform.data.obj.id}} call obviously doesn't work, but I'd like something along those lines.
How do I insert dynamic context from the current object into the admin change form?
Add your extra context in change_view
class MyObjectAdmin(admin.ModelAdmin):
# A template for a very customized change view:
change_form_template = 'admin/my_change_form.html'
def get_dynamic_info(self):
# ...
pass
def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['osm_data'] = self.get_dynamic_info()
return super(MyObjectAdmin, self).change_view(
request, object_id, form_url, extra_context=extra_context,
)
I believe the magic variable you seek is 'original', this contains the python object the change form is editing:
<a href="http://example.com/abc/{{ original.id }}?"/>View Website</a>
I want to display the following two views in a template index.html.
class IndexView(generic.ListView):
template_name = 'items/index.html'
context_object_name = 'popular_items_list'
def get_queryset(self):
return Item.objects.order_by('-rating')[:5]
class tryView(generic.ListView):
template_name = 'items/index.html'
context_object_name = 'latest_items_list'
def get_queryset(self):
return Item.objects.order_by('pub_date')[:5]
Is there a way to combine these two views into one view?
How should I get both query sets displayed on index.html?
Is it possible to send all the Item.objects.all() and filter in the template?
A few questions here, let me answer the first.
You can overwrite get_context_data and add to the context of the template for more items in one view. For example...
class IndexView(generic.ListView):
template_name = 'items/index.html'
context_object_name = 'popular_items_list'
def get_queryset(self):
return Item.objects.order_by('-rating')[:5]
def get_context_data(self, *args, **kwargs):
context = super(IndexView, self).get_context_data(*args, **kwargs)
context['moreItems'] = Item.objects.order_by('pub_date')[:5]
return context
This way you can include multiple query sets on a page/template as required. In this example moreItems would be available in your template along with popular_items_list
In regards to the second question, yes you can pass in the URL arguments and use them to filter the queryset. I suggest reading up on this.
You have two options that I can think of. One option is to get_context_data in the view, which would looks something as follows:
#views.py
class IndexView(generic.ListView):
template_name = 'items/index.html'
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['item_by_rating'] = Item.objects.order_by('-rating')[:5]
context['item_by_pub_date'] = Item.objects.order_by('pub_date')[:5]
return context
And then in your template you could access {{ items_by_rating }} and {{ items_by_pub_date }}
The second option would be to sort the objects in the template, which would allow you to define just one context variable in your view and then sort in different ways in the template using the dictsort template filter. This would look something like this:
# views.py
class tryView(generic.ListView):
template_name = 'items/index.html'
def get_queryset(self):
return Item.objects.all()
# index.html
{% for i in object_list|dictsort:"item.pub_date" %}
{{ i.rating }} {{ i.pub_date }}
{% endfor %}
I think I like the 2nd option more just passing one object_list context item and then sorting in the template. But either way should be fine.