I have a class view which uses FormView. I need to change the name of the form i.e. this is what it used to be in my old function view:
upload_form = ContactUploadForm(request.user)
context = {'upload': upload_form,}
With my new view I'm assuming I can rename using the get_context_data method but unsure how.
How can I rename this form to upload instead of form as my templates uses {{ upload }} not {{ form }}? Thanks.
Current Class View:
class ImportFromFile(FormView):
template_name = 'contacts/import_file.html'
form_class = ContactUploadForm
def get_context_data(self, **kwargs):
"""
Get the context for this view.
"""
# Call the base implementation first to get a context.
context = super(ImportFromFile, self).get_context_data(**kwargs)
return context
Try this:
class ImportFromFile(FormView):
template_name = 'contacts/import_file.html'
form_class = ContactUploadForm
def get_context_data(self, **kwargs):
"""
Get the context for this view.
"""
kwargs['upload'] = kwargs.pop('form')
return super(ImportFromFile, self).get_context_data(**kwargs)
Django 2.0+ provides support for changing context object names. See: Built-in class-based generic views
Making “friendly” template contexts
You might have noticed that our sample publisher list template stores all the publishers in a variable named object_list. While this works just fine, it isn’t all that “friendly” to template authors: they have to “just know” that they’re dealing with publishers here.
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 lowercased 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.
If this still isn’t a good match, you can manually set the name of the context variable. The context_object_name attribute on a generic view specifies the context variable to use:
class PublisherList(ListView):
model = Publisher
context_object_name = 'choose the name you want here'
Related
Using Django, I have a page with a form that is rendered using CreateView. The success_url is the same url as the original form. When the form is submitted and is valid, I want to insert some data into the context.
I'd like to know the most flexible and clear way to add this context data, because the example below (which works) feels sub-optimal for such a (presumably) common task. I'm looking for a more general solution that using SuccessMessageMixin.
class DemoView(CreateView):
template_name = "request-demo.html"
form_class = DemoForm
success_url = reverse_lazy("base:demo")
def form_valid(self, form):
create_and_send_confirmation_email(form)
return self.render_to_response(self.get_context_data(success_message=True))
Most generically you will define the succcess_url to be the DetailView of the object you have just created. In fact, if you do not define a success_url at all, the default get_success_url will return
url = self.object.get_absolute_url()
which is commonly the same thing.
If you want to do something special for a newly created object you might
Redirect to a different URL, such as a subclass of your object DetailView with a custom get_context_data method and/or template name. Or possibly, somewhere completely differerent.
Interrogate the newly created object (in the template and/or get_context_data method) to establish in some application-specific way that it is indeed newly created.
I have a function-based view that is currently working successfully. However, I want to learn how to create the equivalent Class Based View version of this function, using the generic UpdateView class -- though I imagine the solution, whatever it is, will be the exact same for CreateView, as well.
I know how to create and use Class Based Views generally, but there is one line of my function-based view that I have not been able to work into the corresponding UpdateView -- as usual with the Generic Editing Class Based Views, it's not immediately clear which method I need to override to insert the desired functionality.
The specific task that I can't port-over to the CBV, so to speak, is a line that overrides the queryset that will be used for the display of a specific field, one that is defined as ForeignKey to another model in my database.
First, the working function-based view, with highlight at the specific bit of code I can't get working in the CVB version:
#login_required
def update_details(request, pk):
"""update details of an existing record"""
umd_object = UserMovieDetail.objects.select_related('movie').get(pk=pk)
movie = umd_object.movie
if umd_object.user != request.user:
raise Http404
if request.method != 'POST':
form = UserMovieDetailForm(instance=umd_object)
# this is the single line of code I can't get working in UpdateView version:
form.fields['user_guess'].queryset = User.objects.filter(related_game_rounds=movie.game_round)
else:
form = UserMovieDetailForm(instance=umd_object, data=request.POST)
if form.is_valid():
form.save()
return redirect(movie)
context = {'form': form, 'object': umd_object }
return render(request, 'movies/update_details.html', context)
I can recreate every part of this function-based view in UpdateView successfully except for this line (copied from above for clarity):
form.fields['user_guess'].queryset = User.objects.filter(related_game_rounds=movie.game_round)
What this line does: the default Form-field for a ForeignKey is ModelChoiceField, and it by default displays all objects of the related Model. My code above overrides that behavior, and says: I only want the form to display this filtered set of objects. It works fine, as is, so long as I'm using this function-based view.
Side-Note: I am aware that this result can be achieved by modifying the ModelForm itself in my forms.py file. The purpose of this question is to better understand how to work with the built-in Generic Class Based Views, enabling them to recreate the functionality I can already achieve with function-based views. So please, refrain from answering my question with "why don't you just do this in the form itself instead" -- I am already aware of this option, and it's not what I'm attempting to solve, specifically.
Now for the UpdateView (and again, I think it would be the same for CreateView). To start off, it would look essentially like this:
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
model = UserMovieDetail
template_name = 'movies/update_details.html'
form_class = UserMovieDetailForm
login_url = 'login' # used by LoginRequiredMixin
# what method do I override here, to include that specific line of code, which needs
# to occur in the GET portion of the view?
def get_success_url(self):
return reverse('movies:movie', kwargs={'pk': self.object.movie.pk, 'slug': self.object.movie.slug })
The above is a working re-creation of my function-based view, replicating all the behavior except that one important line that filters the results of a specific field's ModelChoiceField display in the Form.
How do I get that line of code to function inside this UpdateView? I've reviewed the methods built-in to UpdateView on the classy class-based views website, and then attempted (by pure guess-work) to over-ride the get_form_class method, but I it didn't work, and I was basically shooting in the dark to begin with.
Note that since the functionality I want to re-create is about the display of items in ModelChoiceField of the form, the desired behavior applies to the GET portion of the view, rather than the POST. So I need to be able to override the form fields before the form is rendered for the first time, just like I did in my function based view. Where and how can I do this in UpdateView?
First, a note not related to form - from raise Http404 in functional view I understand that you want to allow user to access only his own movies. For that in class based view you can override get_queryset method:
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
def get_queryset(self):
return UserMovieDetail.objects \
.filter(user=request.user) \
.select_related('movie')
Now let's move to customizing form.
Option 1 - .get_form()
You can override get_form method of the UpdateView:
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
form_class = UserMovieDetailForm
def get_form(self, form_class=None):
form = super().get_form(form_class)
# add your customizations here
round = self.object.movie.game_round
form.fields['user_guess'].queryset = \
User.objects.filter(related_game_rounds=round)
return form
Option 2 - moving customizations to form class and .get_form_kwargs()
You might prefer to move customization logic from view to form. For that you can override form's __init__ method. If customization logic requires extra information (for example, queryset depends on current user), then you can also override get_form_kwargs method to pass extra parameters to the form:
# views.py
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
form_class = UserMovieDetailForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({'current_user': self.request.user})
return kwargs
# forms.py
class UserMovieDetailForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.current_user = kwargs.pop('current_user')
super().__init__(*args, **kwargs)
# add your customizations here
self.fields['user_guess'].queryset = ...
P.S. In general, great resource for understanding django class based views is https://ccbv.co.uk/
I need to write a DetailView in Django. I achieved this functionality. However, I need to add some more data along with the context object. How will I achieve this.
My generic view is:
class AppDetailsView(generic.DetailView):
model = Application
template_name = 'appstore/pages/app.html'
context_object_name = 'app'
I need to add one more variable to the context object:
response = list_categories(storeId)
How about using get_context_data
class AppDetailsView(generic.DetailView):
model = Application
def get_context_data(self, **kwargs):
context = super(AppDetailsView, self).get_context_data(**kwargs)
context['categories'] = list_categories(storeId)
return 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.
I'm currently learning how to use the class-based views in django 1.3. I'm trying to update an application to use them, but I still don't uderstand very well how they work (and I read the entire class-based views reference like two or three times EVERY day).
To the question, I have an space index page that needs some extra context data, the url parameter is a name (no pk, and that can't be changed, it's the expected behaviour) and the users that don't have that space selected in their profiles can't enter it.
My function-based code (working fine):
def view_space_index(request, space_name):
place = get_object_or_404(Space, url=space_name)
extra_context = {
'entities': Entity.objects.filter(space=place.id),
'documents': Document.objects.filter(space=place.id),
'proposals': Proposal.objects.filter(space=place.id).order_by('-pub_date'),
'publication': Post.objects.filter(post_space=place.id).order_by('-post_pubdate'),
}
for i in request.user.profile.spaces.all():
if i.url == space_name:
return object_detail(request,
queryset = Space.objects.all(),
object_id = place.id,
template_name = 'spaces/space_index.html',
template_object_name = 'get_place',
extra_context = extra_context,
)
return render_to_response('not_allowed.html', {'get_place': place},
context_instance=RequestContext(request))
My class-based view (not working, and no idea how to continue):
class ViewSpaceIndex(DetailView):
# Gets all the objects in a model
queryset = Space.objects.all()
# Get the url parameter intead of matching the PK
slug_field = 'space_name'
# Defines the context name in the template
context_object_name = 'get_place'
# Template to render
template_name = 'spaces/space_index.html'
def get_object(self):
return get_object_or_404(Space, url=slug_field)
# Get extra context data
def get_context_data(self, **kwargs):
context = super(ViewSpaceIndex, self).get_context_data(**kwargs)
place = self.get_object()
context['entities'] = Entity.objects.filter(space=place.id)
context['documents'] = Document.objects.filter(space=place.id)
context['proposals'] = Proposal.objects.filter(space=place.id).order_by('-pub_date')
context['publication'] = Post.objects.filter(post_space=place.id).order_by('-post_pubdate')
return context
urls.py
from e_cidadania.apps.spaces.views import GoToSpace, ViewSpaceIndex
urlpatterns = patterns('',
(r'^(?P<space_name>\w+)/', ViewSpaceIndex.as_view()),
)
What am I missing for the DetailView to work?
The only problem I see in your code is that your url's slug parameter is named 'space_name' instead of 'slug'. The view's slug_field attribute refers to the model field that will be used for slug lookup, not the url capture name. In the url, you must name the parameter 'slug' (or 'pk', when it's used instead).
Also, if you're defining a get_object method, you don't need the attributes queryset, model or slug_field, unless you use them in your get_object or somewhere else.
In the case above, you could either use your get_object as you wrote or define the following, only:
model = Space
slug_field = 'space_name'