How to define the slug of another model in myListView - django

I sort of need help understanding my own code specifically the views.py. I'm trying to change url pattern for my TitleUpdateListView from using my Update models title field and instead using the slug field instead.
If someone could help explain line by line whats going in in my TitleUpdateListView so I could better understand whats specifically going on that would be great.
urls.py
urlpatterns = [
# Update view for each game
path('<str:title>/updates/', TitleUpdateListView.as_view(), name='title-updates'),
# Adds the ability to sort by platform
path('<str:title>/updates/<int:platform_id>/', TitleUpdateAjaxListView.as_view(), name='title-updates-ajax'),
]
views.py
class TitleUpdateListView(ListView):
model = Update
context_object_name = 'updates'
template_name = 'updates/title_updates.html'
def get_queryset(self):
title = get_object_or_404(Game, title=self.kwargs.get('title'))
return Update.objects.filter(game=title).order_by('-date_published')
def get_context_data(self, **kwargs):
context = super(TitleUpdateListView, self).get_context_data(**kwargs)
context['game'] = get_object_or_404(Game, title=self.kwargs.get('title'))
return context
class TitleUpdateAjaxListView(ListView):
model = Update
template_name = 'updates/updates_ajax.html'
context_object_name = 'updates'
paginate_by = 5
def get_queryset(self):
title = get_object_or_404(Game, title=self.kwargs.get('title'))
return Update.objects.filter(game=title, platform=Platform.objects.filter(
id=self.kwargs.get('platform_id')).first()).order_by('-date_published')
def get_context_data(self, **kwargs):
context = super(TitleUpdateAjaxListView, self).get_context_data(**kwargs)
context['game'] = get_object_or_404(Game, title=self.kwargs.get('title'))
return context
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
context = self.get_context_data()
return render(request, self.template_name, context)

Not sure what you meant by "I'm trying to change url pattern for my TitleUpdateListView from using my Update models title field and instead using the slug field instead.". In the urls.py, you can change the name of the parameter (the xxxx in <str:xxxx>) to whatever you want, as long as you also look for this same name in the view. You can change it to <str:slug> and in your view you'd fetch it like self.kwargs.get('slug'). Just remember to also change which parameter are you using to filter the Game table (slug instead of title).
As for explaining what your view does, you should probably take a look at Django's docs on Class Based Views, but I'll try to give an overview:
The get_queryset method is searching the Game table to find the games whose title matches the title passed in the URL parameter. It then returns a list of all Update objects whose game field points to the game just found.
The get_context_data method is adding the same Game object found in the get_queryset method to the view's context under the 'game' key. This means that you can access the Game object inside the template that this view renders.

You just need to change the get_queryset method of your view:
# change url variable name from title to slug
path('<str:slug>/updates/', TitleUpdateListView.as_view(), name='title-updates'),
def get_queryset(self):
# the url variables are stored in the dictionary self.kwargs
slug = self.kwargs.get('slug')
game = get_object_or_404(Game, slug=slug)
return Update.objects.filter(game=game).order_by('-date_published')
The same applies for get_context_data:
def get_context_data(self, **kwargs):
context = super(TitleUpdateListView, self).get_context_data(**kwargs)
context['game'] = get_object_or_404(Game, slug=self.kwargs.get('slug'))
return context

Related

Show a paginated ListView and an UpdateView on the same template page

I am trying to create a Django page where something can be updated and something can be viewed in a paginated table. The model looks like this:
class CostGroup(models.Model):
name = models.CharField(max_length=200)
description = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse(
'costgroup_detail',
kwargs={
'costgroup_pk': self.pk,
}
)
class Cost(models.Model):
cost_group = models.ForeignKey(CostGroup)
amount = models.DecimalField(max_digits=50, decimal_places=2)
def get_absolute_url(self):
return reverse(
'cost_detail',
kwargs={
'cost_pk': self.pk,
}
)
So the edit form is for the name and description fields of the CostGroup model and the table should show a list of the 'amounts`
I previously had it working by just having an UpdateView for the form and the table included in the form template. Now though, as I want to include pagination on the table, I need to use two views on the same page. The page I have designed should look something like this in the end:
I am not worried about the styling at the moment my main focus at the moment is getting the form and the table on the same page. In its current state the only thing that I don't have is the pagination for the table:
The view currently looks like this:
class CostDetail(UpdateView):
model = models.Cost
pk_url_kwarg = 'cost_pk'
template_name = 'main/cost_detail.html'
form_class = forms.CostDetailEditForm
success_url = reverse_lazy('cost_list')
I have a feeling that leveraging the underlying mixins that the Django CBVs use is probably the way to go but I am not sure how to begin with this.
Any help would be much appreciated
Thanks for your time
(This clarification seemed to work better as a new answer)
It looks like you're dealing with both of the tables. The object level is using CostGroup, while the List view is showing the child records from Cost linked to a CostGroup. Assuming that is true, here's how I would proceed:
class CostDetail(ModelFormMixin, ListView):
model = CostGroup # Using the model of the record to be updated
form_class = YourFormName # If this isn't declared, get_form_class() will
# generate a model form
ordering = ['id']
paginate_by = 10
template_name = 'main/cost_detail.html' # Must be declared
def get_queryset(self):
# Set the queryset to use the Cost objects that match the selected CostGroup
self.queryset = Cost.objects.filter(cost_group = get_object())
# Use super to add the ordering needed for pagination
return super(CostDetail,self).get_queryset()
# We want to override get_object to avoid using the redefined get_queryset above
def get_object(self,queryset=None):
queryset = CostGroup.objects.all()
return super(CostDetail,self).get_object(queryset))
# Include the setting of self.object in get()
def get(self, request, *args, **kwargs):
# from BaseUpdateView
self.object = self.get_object()
return super(CostDetail,self).get(request, *args, **kwargs)
# Include the contexts from both
def get_context_data(self, **kwargs):
context = ModelFormMixin.get_context_data(**kwargs)
context = ListView.get_context_data(**context)
return context
# This is the post method found in the Update View
def post(self, request, *args, **kwargs):
# From BaseUpdateView
self.object = self.get_object()
# From ProcessFormView
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
I haven't tried to run this, so there may be errors. Good luck!
(Remember ccbv.co.uk is your friend when digging into Class-based Views)
An app I'm working on now uses a similar approach. I start with the ListView, bring in the FormMixin, and then bring in post() from the FormView.
class LinkListView(FormMixin, ListView):
model = Link
ordering = ['-created_on']
paginate_by = 10
template_name = 'links/link_list.html'
form_class = OtherUserInputForm
#=============================================================================#
#
# Handle form input
#
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
def get_success_url(self):
return reverse('links')
You may also wish to override get_object(), get_queryset(), and get_context().

Django - dynamic success_url in UpdateView

I want my user to be able to see a page, update that page, and then be returned to that page or continue making more edits.
Here's the view to show the information:
# urls.py
url(r'^gameview/$', views.GameView.as_view(template_name='matchview.html'), name='GameView')
# Views.py
class GameView(generic.TemplateView):
template_name = "matchview.html"
def get_context_data(self, **kwargs):
context = super(GameView, self).get_context_data(**kwargs)
q = self.request.GET.get('match')
context['report'] = GameNotes.objects.filter(game=q)
context['game'] = Schedule.objects.get(match=q)
context['gamepic'] = Photo.objects.filter(game=q)
return context
So now they want to add information about a game. I use an UpdateView
class GameView(generic.TemplateView):
template_name = "matchview.html"
def get_context_data(self, **kwargs):
context = super(GameView, self).get_context_data(**kwargs)
q = self.request.GET.get('match')
context['report'] = GameNotes.objects.filter(game=q)
context['game'] = Schedule.objects.get(match=q)
context['gamepic'] = Photo.objects.filter(game=q)
return context
When the user finishes updating in the latter view, they should be returned to the former on the exact same team. This post helped me move in the right direction insofar as using 'get_success_url', but I'm still stuck because I don't think I'm using kwargs. Everything I've tried has resulted in errors.
(My (gulp) thought is that I should re-write the urls to use PKs so that this stuff is easier, but I wanted to make sure)
EDIT:
My fugly attempt (Note: I Have two submit buttons, one to update and one to update and add notes).
def form_valid(self, form):
if form.is_valid():
form.save()
if 'submit' in self.request.POST:
q = self.request.GET.get('match')
return reverse_lazy('TeamView', args=(q))
else:
return render('addnotes', {'game' : q})
SOLUTION:
Learned how to use URL Parameters and kwargs:
(for anyone new like me, self.kwargs.get is brilliant)
def get_success_url(self, **kwargs):
q = self.kwargs.get('match')
if "submit" in self.request.POST:
url = reverse('GameView', args={q : 'match'})
else:
url = reverse('AddNotes', args={q : 'match'})
return url
What about get_absolute_url for the model object?
https://docs.djangoproject.com/en/1.10/ref/models/instances/#get-absolute-url
from django.urls import reverse
class GameModel(models.Model):
....
def get_absolute_url(self):
return reverse('game:single_page', args=[str(self.id)])
And in your GameView:
class GameView(generic.TemplateView):
template_name = "matchview.html"
def get_context_data(self, **kwargs):
....
def get_success_url(self, **kwargs):
return self.object.get_absolute_url()

passing variable from url to CreateView

I am looking for a way to automatically fill in the ForeignField ID based on the url. The user will click a link, pointing to the CreateView with the id of the ForeignField added to the end of it. I am unsure of the best way of doing this.
urls.py
url(r'^comment/(?P<pk>[0-9]+)/$', CommentCreate.as_view()),
views.py
class CommentCreate(CreateView):
form_class = CommentCreateForm
model = Comment
queryset = Comment.objects.all()
html-link.html
Comment
As far as I know queryset is invalid for CreateView. Of course you can set it, but it does not make a sense, because when you use CreateView it is supposed that you want to create an object.
I guess you can try something like:
class CommentCreate(CreateView):
form_class = CommentCreateForm
model = Comment
form_class = FollowupForm
def get_form(self):
form = super(CommentCreate, self).get_form(self.form_class)
# artical_id - is a name of foreign key defined the Comment model.
form.instance.artical_id = Artical.objects.get(pk=self.kwargs.get('pk', None))
return form
That way you can spend the article in the context also
def get_context_data(self, **kwargs):
context = super(CommentCreate, self).get_context_data(**kwargs)
context['artical'] = Artical.objects.get(pk=self.kwargs[artical_id])
return context
It can be corrected by doing filter like this:
context['artical'] = Artical.objects.filter(pk=self.kwargs[artical_id])
It is more suggestive if your url is like this:
#urls.py
url(r'^comment/(?P<artical_id>[0-9]+)/$', CommentCreate.as_view()),
#views.py
class CommentCreate(CreateView):
form_class = CommentCreateForm
model = Comment
queryset = Comment.objects.all()
#That way you can spend the article in the context also
def get_context_data(self, **kwargs):
context = super(CommentCreate, self).get_context_data(**kwargs)
context['artical'] = Artical.objects.get(pk=self.kwargs[artical_id])
return context

Django pass URL parameter to custom FormMixin

I have a page in my web app that can be accessed by the URL like this:
http://localhost:8000/organizations/list_student/?school_id=19
I'd like access to the school_id from the URL above for one of the form mixins, named PhoneNumberMixin (please see below). Could someone who's knowledgeable about Django tell me how I should pass that URL parameter into custom form mixin like PhoneNumberMixin? Thank you.
In models.py:
class Student(models.Model):
school = models.ForeignKey(School)
phone_number = models.CharField(max_length=15, blank=True)
In urls.py:
urlpatterns = patterns('',
# There are more, but to save space, only relevant part is included
url(r'^list_student/$', StudentListView.as_view(), name='list_student'),
)
In views.py for the page:
class StudentListView(LoginRequiredMixin, FormView):
form_class = SchoolAddStudentForm
template_name = 'organizations/list_student.html'
def get_success_url(self):
return reverse_lazy('organizations:list_student') + '?school_id=' + self.request.GET['school_id']
def get_form(self, form_class):
request = self.request
return form_class(request, **self.get_form_kwargs())
def get_context_data(self, **kwargs):
# add stuff to data to pass to HTML page here
return data
def form_valid(self, form):
data = form.cleaned_data
# save cleaned data to DB here
return HttpResponseRedirect(self.get_success_url())
In forms.py,
# Note PhoneNumberFormMixin below. It is used to clean phone numbers
# such duplicate checking against the existing numbers in the DB
class SchoolAddStudentForm(PhoneNumberFormMixin, forms.Form):
phone_numbers = forms.CharField(widget=forms.Textarea(attrs=form_attrs))
def __init__(self, request, *args, **kwargs):
super(SchoolAddStudentForm, self).__init__(*args, **kwargs)
self.fields['phone_numbers'].label = 'Step 1 (required): Add comma-separated list of phone numbers [E.g., 5856261234, 8613910912345]:'
In mixins.py:
class PhoneNumberFormMixin(object):
"""
Custom form mixin for validating phone numbers
"""
def clean_phone_numbers(self):
data = self.data
numbers = []
sid = #!!!! this is where I'd like to access school_id from the URL
qs = Student.objects.filter(school_id=sid)
# do something with the qs
return ','.join(numbers)
I'm not sure I have a full picture as you're missing views.py & urls.py. But generally, field cleaning methods should only check that an input is correctly formatted, and the actual application logic should be located in your view's form_valid() method. Form() methods don't have access to HTTP request information precisely because it is outside the scope of their functionality.
From your view, you can access the URL parameter with:
self.request.GET.get('school_id', None)
Read up on form_valid() -- this is where you should add code to modify an object + field values before it's saved, and / or create related objects if needed.
In my usecase I'm doing a search which displays a list of search results.
I ended up using this:
class SearchView(FormMixin, ListView):
def get_queryset(self):
qs = super().get_queryset()
# TODO implement filtering
return qs
def get_form_kwargs(self):
# use GET parameters as the data
kwargs = super().get_form_kwargs()
if self.request.method in ('GET'):
kwargs.update({
'data': self.request.GET,
})
return kwargs

Django - Generic View Subclassed - url Parameters

I need to display a detail page for a video with some other data.
For that I use DetailView that I have overridden to add some variables to the context.
Here are the code parts:
#urlconf
#...
(r'viewtube/(?P<pk>\d+)$', VideoFileDetailView.as_view()),
#...
#view
class VideoFileDetailView(DetailView):
model = VideoFile
def get_context_data(self, **kwargs):
context = super(VideoFileDetailView, self).get_context_data(**kwargs)
# context['rates'] = VideoRate.objects.filter(video=11, user=1)
return context
Here pk is the id of a video, I need to get the rates of the selected video by the current user.
It would have been useful to show the models. But I think you need to override get(), not get_context_data, as unfortunately the latter doesn't get passed the request, which you need in order to get the user. So:
def get(self, request, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
context['rates'] = VideoRate.objects.filter(video=self.object, user=request.user)
return self.render_to_response(context)
The request should be accessible at self.request. self.request is set at the beginning of the request (in View.dispatch) and should be available any of the subclass methods.
class VideoFileDetailView(DetailView):
model = VideoFile
def get_context_data(self, **kwargs):
context = super(VideoFileDetailView, self).get_context_data(**kwargs)
context['rates'] = VideoRate.objects.filter(video=11, self.request.user)
# note that the object is available via self.object or kwargs.get("object")
return context