passing variable from url to CreateView - django

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

Related

How to define the slug of another model in myListView

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

Clumsy repeat-myself django permission check

I have a class based view for a Problem object. I only want the author of the Problem to be able to view the Problem cbv. Other logged-in users should be redirected to a forbidden page.
I achieve this by checking the ownership in the get_template_name() method. But if I want to pass in context to the forbidden template, I also need to check ownership in the get_context_data() and make the appropriate context.
This works, but it seems like way too much semi-repetitive code, and just very non-Pythonic/Djangonic.
Can any suggest a nicer way to do this? It's an issue for many ClassBasedViews I created. I have both "Problem" objects and "Board" objects that I want to ensure the logged-in user == the author of the Problem or Board object. Almost seems like I could have some kind of Mixin or something.
Anyway, here is an example of my approach:
class ProblemStudyView(UpdateView):
model = Problem
fields = "__all__"
def get_template_names(self):
problem = self.get_object()
if problem.author != self.request.user:
# User should not see this entry
context = {'original_author': problem.author, 'request_user': self.request.user}
template_name = "board/forbidden.html"
return template_name
else:
# This user is OK
template_name = "board/study.html"
return template_name
def get_context_data(self, **kwargs):
context = super(ProblemStudyView, self).get_context_data(**kwargs)
problem = self.get_object()
# Do the permission check a second time to setup correct context
if problem.author != self.request.user:
context = {'original_author': problem.author, 'request_user': self.request.user}
return context
else:
# User is ok; proceed as normal
return context
You could use the PermissionRequiredMixin and a custom Permission.
I'd do something like this:
from django.contrib.auth.mixins import PermissionRequiredMixin
class ProblemStudyView(PermissionRequiredMixin, UpdateView):
model = Problem
fields = "__all__"
permission_denied_message = 'YOURMESSAGEHERE'
raise_exception = True
template_name = 'board/study.html'
def dispatch(self, request, *args, **kwargs):
self.request = request # needed for has_permission
self.kwargs = kwargs # needed for get_object
return super().dispatch(request, *args, **kwargs)
def has_permission(self):
problem = self.get_object()
return problem.author == self.request.user
Since a PermissionDeniedException is raised, you can use (or alter) the standard Django http forbidden view:
https://docs.djangoproject.com/en/1.11/ref/views/#the-403-http-forbidden-view

Django model formset performance

I have little perfomance issue with Django 1.4.2 and PostgreSQL 9.1. I want to create model fromset with form created like this:
forms.py
class AcknowledgeForm(forms.ModelForm):
class Meta:
model = Attendance
fields = ['acknowledge', ]
widgets = {'acknowledge': forms.CheckboxInput()}
AcknowledgeFormset = forms.models.modelformset_factory(Attendance, form=AcknowledgeForm, extra=0)
for model with few ForeignKeys
models.py
class Attendance(models.Model): #500k rows in DB
zamestnani = models.ForeignKey('people.Zamestnani', related_name='attendance') #~1k rows
day = models.ForeignKey(Day) #~2.5k rows
acknowledge = models.NullBooleanField(blank=True, null=True)
views.py
class VacationAcknowledgeView(LoginRequiredMixin, TemplateView):
template_name = "presence/presence_vacation_acknowledge.html"
http_method_names = ['get', 'post']
def get_context_data(self, **kwargs):
context = super(VacationAcknowledgeView, self).get_context_data()
person = Person.fromRequest(self.request)
first_day = date(date.today().year, 1, 1)
days = Attendance.objects.filter(acknowledge=None, day__date__gte=first_day, zamestnani__osoba=person)
context['formset'] = AcknowledgeFormset(queryset=days)
return context
def post(self, request, *args, **kwargs):
#next line is screwed
formset = AcknowledgeFormset(request.POST)
#never been there....
return super(VacationAcknowledgeView, self).get(request, *args, **kwargs)
I can create it, render it, everything seems ok, but assigning data from POST leads to server freeze for very looooong time (litteraly hours) for single object.
After short digging around I personaly blame model formset, because when I create it and process it just like single form, everything working as expected. But I have no idea how to fix/evade this.
Thank for any reasonable advice.
You mentioned that there are ~500,000 rows in your database for the Attendance models. In Django, when the queryset parameter is not specified for a ModelFormSet, it includes all objects in that model. Django is probably fetching all 500,000 rows of data.
You need to figure out the queryset for your ModelFormSet. E.g.
def post(self, request, *args, **kwargs):
queryset = Attendance.objects.none()
formset = AcknowledgeFormset(queryset=queryset, data=request.POST)
# continue with your regular code execution
The documentation contains a section for the queryset of a ModelFormSet: https://docs.djangoproject.com/en/1.9/topics/forms/modelforms/#django.forms.models.BaseModelFormSet

How do I use an UpdateView to update a Django Model?

I'm trying to update a model in Django using the class-based generic view UpdateView.
I read the page Updating User model in Django with class based UpdateView to try and get me started, but I'm getting an error 'WSGIRequest' object has no attribute 'id'
I'm a fresh face to Django, so please be forgiving if I'm doing something stupid.
//urls.py
url(r'^portfolios/update/(?P<id>\d+)/$',PortfoliosUpdateView.as_view()),
//views.py
class PortfoliosUpdateView(UpdateView):
form_class = PortfoliosCreateForm
model = Portfolios
template_name = 'portfolios/create.html'
def get(self, request, **kwargs):
self.object = Portfolios.objects.get(id=self.request.id)
form_class = self.get_form_class()
form = self.get_form(form_class)
context = self.get_context_data(object=self.object, form=form)
return self.render_to_response(context)
def get_object(self, queryset=None):
obj = Portfolios.objects.get(id=self.request.id)
return obj
It's mostly just a modified version of the code originally posted, but I thought it'd work. I know that I'm trying to retrieve the id passed as a GET parameter, but that doesn't seem to come through in the request variable. Am I going about this the wrong way?
Thanks
Edit: I think I fixed it, but this may be wrong:
I changed the lines
self.object = Portfolios.objects.get(id=self.request.id)
obj = Portfolios.objects.get(id=self.request.id)
to
self.object = Portfolios.objects.get(id=self.kwargs['id'])
obj = Portfolios.objects.get(id=self.kwargs['id'])
I could be wrong.
It should be:
def get_object(self, queryset=None):
obj = Portfolios.objects.get(id=self.kwargs['id'])
return obj
Look at class based generic view dispatch explains that keyword arguments are assigned to self.kwargs.:
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
self.request = request
self.args = args
self.kwargs = kwargs
return handler(request, *args, **kwargs)
id = self.request.GET.get('id',None) is what you needed when trying to access the GET query string.
However, your view can be simplified:
from django.conf.urls import *
from django.views.generic import UpdateView
from yourapp.models import Portfolios
from yourapp.forms import PortfoliosCreateForm
urlpatterns = patterns('',
url('^portfolios/update/(?P<pk>[\w-]+)$', UpdateView.as_view(
model=Portfolios,
form_class=PortfoliosCreateForm,
template_name='portfolios/create.html',
success_url='/portfolios'
), name='portfolio_update'),
)
views.py
class MyUpdateView(UpdateView):
model = ModelName # required
template_name = 'x/h1.html'
form_class = ModelNameForm
success_url = reverse_lazy('app:page1')
def get_queryset(self):
"""
Optional condition to restrict what users can see
"""
queryset = super().get_queryset()
return queryset.filter(id__lt=20)
def get_success_url(self):
return reverse_lazy(
'app1:abc',
kwargs={'pk': self.object.id}
)
urls.py
In urlpatterns=[]
path('xyz/<pk>/', MyUpdateView.as_view(),name='xyz')
my_model_view.html
{{form}}
You will be able to edit ModelName at url /xyz/<pk>/ where <pk> can be anything from 1 to 20 based on our condition in get_queryset(). Take that condition out to allow users to edit any object.
self.object is only available after post request to the UpdateView.

How to pass a queryset to a ModelChoiceField using a self.field_value in Django ModelForms

I could explain the whole thing to you but I guess a code speaks clearer than words so:
class Skills(models.Model):
skill = models.ForeignKey(ReferenceSkills)
person = models.ForeignKey(User)
class SkillForm(ModelForm):
class Meta:
model = Skills
fields = ( 'person', 'skill')
(???)skill = forms.ModelChoiceField(queryset= SkillsReference.objects.filter(person = self.person)
I'm just guessing at how I can do it. But I hope you guys understand what I'm trying to do.
You can ovverride a form structure before you create an instance of the form like:
class SkillForm(ModelForm):
class Meta:
model = Skills
fields = ( 'person', 'skill')
In your view:
SkillForm.base_fields['skill'] = forms.ModelChoiceField(queryset= ...)
form = SkillForm()
You can override it anytime you want in your view, impottant part is, you must do it before creating your form instance with
form = SkillForm()
Assuming you are using class-based views, you can pass the queryset in your form kwargs and then replace it on form init method:
# views.py
class SkillUpdateView(UpdateView):
def get_form_kwargs(self, **kwargs):
kwargs.update({
'skill_qs': Skills.objects.filter(skill='medium')
})
return super(self, SkillUpdateView).get_form_kwargs(**kwargs)
# forms.py
class SkillForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
qs = kwargs.pop('skill_ks')
super(self, SkillForm).__init__(*args, **kwargs)
self.fields['skill'].queryset = qs
But, personally I prefer this second approach. I get the form instance on the View and than replace the field queryset before django wrap it on the context:
# views.py
class SkillsUpdateView(UpdateView):
form_class = SkillForm
def get_form(self, form_class=None):
form = super().get_form(form_class=self.form_class)
form.fields['skill'].queryset = Skills.objects.filter(skill='medium')
return form
Your code looks almost ok. Try this SkillForm:
class SkillForm(ModelForm):
skill = forms.ModelChoiceField(queryset= SkillsReference.objects.filter(person = self.person)
class Meta:
model = Skills
fields = ( 'person', 'skill')
The difference is that skill is a form's field, should not be in Meta class
EDITED
The above solution is incorrect, but this link describes how to achieve what you want:
http://www.zoia.org/blog/2007/04/23/using-dynamic-choices-with-django-newforms-and-custom-widgets/