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.
Related
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().
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
I would like to return to the User related record.
Somebody can help me?
part of my view
class UserProfileDetailView(DetailView):
model = get_user_model()
slug_field = "username"
template_name = "perfil.html"
def get_object(self, queryset=None):
user = super(UserProfileDetailView, self).get_object(queryset)
UserProfile.objects.get_or_create(user=user)
return user
Something like a old way>
def my_view(request, slug):
var = get_object_or_404(Model, slug=slug)
xxx = AnotherModel.objects.filter(var=var)
...
how can i perfome this in the first view UserProfileDetailView,
show related data?
What I do in this case is add the related object into the context data. It would be something like this:
class UserProfileDetailView(DetailView):
model = get_user_model()
slug_field = "username"
template_name = "perfil.html"
def get_context_data(self, **kwargs):
# xxx will be available in the template as the related objects
context = super(UserProfileDetailView, self).get_context_data(**kwargs)
context['xxx'] = AnotherModel.objects.filter(var=self.get_object())
return context
Another approach is to extend DetailView with MultipleObjectMixin, as in this example:
from django.views.generic import DetailView
from django.views.generic.list import MultipleObjectMixin
from django.contrib.auth import get_user_model
class DetailListView(MultipleObjectMixin, DetailView):
related_model_name = None
def get_queryset(self):
# a bit of safety checks
if not hasattr(self, "related_model_name"):
raise AttributeError(
"%s.related_model_name is missing." % (
self.__class__.__name,))
if not self.related_object_name:
raise NotImplementedError(
"%s.related_model_name must not be None." % (
self.__class__.__name,))
# get the object
obj = self.get_object()
# get the related model attached to the object
related_model = getattr(obj, "%s_set" % self.related_model_name, None)
# safety check if related model doesn't exist
if not related_model:
raise AttributeError(
"%s instance has no attribute \"%s_set\"" % (
obj.__class__.__name__, self.related_model_name)
# return the related model queryset
return related_model.all()
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
return super(DetailListView, self).get(request, *args, **kwargs)
class UserProfileDetailView(DetailListView):
template_name = "perfil.html"
model = get_user_model()
slug_field = "username"
related_model_name = "anothermodel"
def get_object(self, queryset=None):
user = super(UserProfileDetailView, self).get_object(queryset)
UserProfile.objects.get_or_create(user=user)
return user
In my opinion this approach is a little less cleaner and understandable, but it has a huge advantage in reusability. It definitely has one downside: if you are using the class variable context_object_name, it will refer to the related objects list and not to the object itself (this has to do with how the inheritance chain is set up when constructing the class).
I've seen a lot of answers about how this is solved when not using Class Based Views. Is there some really obvious thing I'm missing about doing it with CBVs?
Basically I want to have a MultipleChoiceField in my form which has choices determined by what is happening in the view. e.g. I use the PK from the URL to do some backend requests and then those should be used to populate the choices.
# forms.py
from django.forms import Form, MultipleChoiceField, CharField
class EmailForm(Form):
users = MultipleChoiceField(required=False)
subject = CharField(max_length=100)
message = CharField()
def __init__(self, users=None, *args, **kwargs):
super(EmailForm, self).__init__(*args, **kwargs)
if users:
self.fields['users'].choices = users
#urls.py
from django.conf.urls import url, patterns
from .views import EmailView
# url patterns
urlpatterns = patterns('',
url( r'^(?P<pk>\d+)$', EmailView.as_view(), name="maindex" ),
)
#views.py
from django.views.generic import FormView, TemplateView
from .forms import EmailForm
class EmailView(FormView):
template_name = 'myapp/email.html'
form_class = EmailForm
success_ulr = '/thanks/'
def form_valid(self, form):
# Do stuff here
return super(EmailView, self).form_valid(form)
Basically it boils down to how/where to call the init function from the view. How do I do that? Or is there another way I've missed? I thought of overriding get_form_kwargs in the view, but couldn't make that do anything.
Thanks
The view:
from django.views.generic import FormView
class EmailView(FormView):
# ...
def get_form_kwargs(self):
kwargs = super(EmailView, self).get_form_kwargs()
# get users, note: you can access request using: self.request
kwargs['users'] = users
return kwargs
The form:
from django import forms import Form
class EmailForm(Form):
users = MultipleChoiceField(required=False)
# ...
def __init__(self, *args, **kwargs):
self.users = kwargs.pop('users', None)
super(EmailForm, self).__init__(*args, **kwargs)
self.fields['users'].choices = self.users
Basically, what I've done in a similar case is the following (Python 3.5, Django 1.8):
def get_form(self, *args, **kwargs):
form= super().get_form(*args, **kwargs)
form.fields['rank'].choices= <sequence of 2-tuples>
return form
where obviously rank is the field name. This way I use the default form.
Alright, the FormMixin calls get_form to get the form-class which looks like
def get_form(self, form_class):
"""
Returns an instance of the form to be used in this view.
"""
return form_class(**self.get_form_kwargs())
So you can either override get_form to instance your Form yourself
def get_form(self, form_class):
return EmailForm(files=self.request.FILES or None,
data=self.request.POST or None,
users=some_user_queryset)
or stay a bit more generic and override get_form_kwargs to something like
def get_form_kwargs(self):
form_kws = super(EmailView, self).get_form_kwargs()
form_kws["users"] = some_user_queryset
return form_kws
One way to do it is:
class EmailView(FormView):
# ...
def get(self, request, *args, **kwargs):
self.users = ...
return super(EmailView, self).get(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(EmailView, self).get_form_kwargs()
kwargs['users'] = self.users
return kwargs
This allows you to set the user choices in the view and to pass them to the form.
You can override get_form.
I needed to update the choices for ChoiceField based on logged in user.
Form:
class InteractionCreateForm(forms.Form):
device = forms.ChoiceField(choices=[(None, '----------')])
...
View:
class InteractionCreateView(FormView):
form_class = InteractionCreateForm
...
def get_form(self, form_class=None):
form_class = super().get_form(form_class=None)
form_class.fields['device'].choices = \
form_class.fields['device'].choices \
+ [(device.pk, device) for device in Device.objects.filter(owner=self.request.user.id)]
return form_class
Hi Stackoverflow people,
In my clean function in forms.py, I would like to save automatically some information in a session variable. However, I do not seem to get access to the request variable.
All examples for handing over the request variable are based on function based views, but here I am using a class based view.
My forms.py:
from django import forms
from item.models import Item
class CreateItemForm(forms.ModelForm):
class Meta:
model = Item
fields = ('name', 'description')
def __init__(self, request, *args, **kwargs):
self.request = request
super(CreateItemForm, self).__init__(*args, **kwargs)
def clean(self):
cleaned_data = super(CreateItemForm, self).clean()
if cleaned_data.get("address"):
self.request.session['name'] = cleaned_data.get("name")
else:
raise forms.ValidationError(_('Oops, can\'t find location.'))
return self.cleaned_data
My views.py:
from django.views.generic.edit import FormView
from item.forms import CreateItemForm
class ItemCreate(FormView):
form_class = CreateItemForm
template_name = 'item/item_create.html'
success_url = 'http://www.google.com'
What is the best way to hand over the request variable from the views.py to forms.py?
Thank you for your answer.
You can overwrite the FormMixin's get_form_kwargs method to add the request for to the form's init parameters:
class ItemCreate(FormView):
def get_form_kwargs(self):
kwargs = super(ItemCreate, self).get_form_kwargs()
kwargs.update({
'request' : self.request
})
return kwargs
Overriding the form.get_initial() works for me
class ItemCreate(FormView):
def get_initial(self):
init = super(ItemCreate, self).get_initial()
init.update({'request':self.request})
return init
And in the clean we can access it with form.initial dict
class sampleForm(forms.Form):
...
...
def clean(self):
user_request = self.initial['request']
By the way, we don't need to pop the extra args like suggested above.