I've been trying to create a Django generic deleteview, to delete an instance of a model.
I however have to check whether it is allowed to delete this item. This is done using a method defined in the model.
So far I've created this:
#login_required
def delete_employee(request, pk):
employee = None
try:
employee = Employee.objects.get(pk=pk)
except:
pass
if employee and not employee.empty():
return render(request, "error.html", None)
else:
# Load the generic view here.
Is this a decent way to go? And how can I load the generic view there?
I've tried things like EmployeeDelete.as_view() but those things don't work.
Or is there a way to check this in the generic view itself?
I've tried that as well, but I wasn't able to load an error page, just throw errors.
To do this with a DeleteView you can do this just by overriding the delete method on your inherited view. Here is an example based on what you have said. This is just an example of how you can accomplish it. You might need to tweak it for your exact scenario, specifically the else on can_delete
class EmployeeDeleteView(DeleteView):
success_url = reverse_lazy('index')
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
can_delete = self.object.can_delete()
if can_delete:
return super(EmployeeDeleteView, self).delete(
request, *args, **kwargs)
else:
raise Http404("Object you are looking for doesn't exist")
Related
So, i have a rather usual "update item" page that is a class-based view which inherits UpdateView. (in views.py it looks like "class ItemUpdateView(UpdateView) and it has method get_success_url(self) defined which contains the redirect url where user will be taken after clicking "Update" button.
My problem is that in my application, there are two different pages that could lead me to this "Update item" page, and depending on the page that user comes from - i want to take the user back to either pageA or pageB upon the successful update of the item.
I wasn't able to find the best-practices of how to handle this anywhere on the web, so - would really appreciate the help.
My guess is that I need to create an additional parameter that will be a part of the url and will contain A or B depending on the pageA or pageB that user came from, i.e. the url itself would be something like '/itemUpdate/int:pk/sourcepage' => '/itemUpdate/45/A'. Does that sound like a correct aproach or is there a better way?
There is a better way that you can check Meta dictionary in request:
write in your views file:
class ItemUpdateView(UpdateView):
previous_url = ''
form_class = UpdateItem
def get(self, request, *args, **kwargs):
self.previous_url = request.META.get('HTTP_REFERER')
print(self.previous_url)
return super().get(request, *args, **kwargs)
def get_initial(self):
initial = super().get_initial()
initial['success_url'] = self.previous_url
return initial
def form_valid(self, form):
self.success_url = form.cleaned_data['success_url']
print(self.success_url)
return super().form_valid(form)
# also you can use get_success_url instead of form_valid()
# def get_success_url(self):
# return super().get_form().cleaned_data['success_url']
and then write a hidden field in your form and name it success_url
class UpdateItem(forms.ModelForm):
success_url = forms.URLField(widget=forms.HiddenInput)
class Meta:
model=Item
fields=['itemName','quantity']
Note you can not use instance in order to get success_url field, because this field belong to form nor your model instance !
refer to documentions
Edited: I am trying to update the value of a single field inside one of Django's objects. Here is the code:
class TodoCompleteView(generic.DetailView):
queryset = Todo.objects.all()
def get_object(self):
# Call the superclass
object = super(TodoCompleteView, self).get_object()
# Record the last accessed date
object.todo_completed = True
object.save()
# Return the object
return object
However, I keep getting an error:
TemplateDoesNotExist at /8/complete
list/todo_detail.html
How can avoid this? I simply want this view to flip a certain value in DB.
You inherit view from DetailView class, which by default is to view some models and not to change. Also, apparently, you use GET request to change the data. This is the wrong approach.
Alternatively I advise you to try to make inherit your view from SingleObjectMixin and View and manually create a handler for POST request.
I would rewrite your example like this:
from django.views.generic import View
from django.views.generic.detail import SingleObjectMixin
class TodoCompleteView(SingleObjectMixin, View):
model = Todo
def post(self, *args, **kwargs):
self.object = self.get_object()
self.object.todo_completed = True
self.object.save(update_fields=('todo_completed', ))
return HttpResponse(status=204)
P.S. you get the error, because DetailView subclassed from SingleObjectTemplateResponseMixin which tries to render the template called <model_name>_detail.html.
I have a view where the user should be able to update an instance of a model, and also update or create new instances of a model related to the first one. I tryied using formsets to do this, and it works perfeclty for creating new objects, but i'm not finding a way to show the objects that already have been created. My problem is that i don't know how to populate the formsets with the existing data, so that i can put it in the context
So this are my models:
class Order(Model):
...
class invoice(Model):
order = models.ForeignKey(Order)
...
And my view is something like this:
class OrderDetailView(UpdateView):
invoice_form_class = InvoiceForm
def get_context_data(self, **kwargs):
context = super(OrderDetailView, self).get_context_data(**kwargs)
if not 'invoice_formset' in context:
context['invoice_formset'] = formset_factory(self.invoice_form_class, extra=3, can_delete=True, formset=BaseFormSet)
return context
There's probably an easy way to do this, but i'm not finding it anywhere
EDIT:
Thanks to #mariodev, i've learned about the inline_formsetfactory, and i'm using it. Now i can fill the formsets with the existing data, and i can create and alter existing ones, but when i try to delete them, nothing happens.
So now i'm defining this formset:
InvoiceFormset = inlineformset_factory(Order, Invoice, fields=('code',), can_delete=True, extra=0)
and my view looks like:
class OrderDetailView(UpdateView):
invoice_form_class = InvoiceForm
def get_context_data(self, **kwargs):
context = super(OrderDetailView, self).get_context_data(**kwargs)
if not 'invoice_formset' in context:
context['invoice_formset'] = InvoiceFormset(instance=self.get_object())
return context
def post(self, *args, **kwargs):
data = self.request.POST
order = self.get_object()
form = self.form_class(data)
invoice_formset = InvoiceFormset(data, instance=order)
if form.is_valid() and invoice_formset.is_valid():
self.object = form.save(order)
for f in invoice_formset:
f.save(self.object)
return HttpResponseRedirect(reverse('order_detail', kwargs={'order_id': self.get_object().order_id}))
I could add some extra lines in the post() to check if i have to delete the form, but it doesn't seem right for me to do it in the view. Is there something else i'm missing?
EDIT AGAIN:
Ended up finding this link which fix exactly this last problem i was having, so now it's all good!
I think it's better use normal function based views (FBV) for this. Understand what's going on first and then gradually move to CBV if you really need to.
This will help you with FBV:
http://catherinetenajeros.blogspot.com/2013/03/inline-formset-saving-and-updating-two.html
This may help you with CBV:
django class-based views with inline model-form or formset
I had a function based view that looked like this:
def account_details(request, acc_id):
account = get_object_or_404(Account, pk=acc_id, person__user=request.user)
# ...
Which shows you details of your account on success, and 404 if you don't have permissions to access the account or it doesn't exist.
I was trying to implement the same using a class based view (extending DetailView), and came up with this:
class AccountDetailView(DetailView):
def get_object(self, queryset=None):
obj = super(AccountDetailView, self).get_object(queryset)
if obj.person.user != self.request.user:
raise Http404()
return obj
The urlconf:
url(r'^account_details/(?P<pk>[0-9a-f]{24})$',
login_required(AccountDetailView.as_view(model=Account)),
name='account_details'),
This attitude works, but introduces 2 extra queries, and looks wrong.
Is there a standard or a more elegant way to achieve the same result?
What arguments would you need to pass to get_queryset anyways? This should do it:
def get_queryset(self):
qs = super(MyView, self).get_queryset()
return qs.filter(person__user=self.request.user)
If you are worried about the queries, you can use select_related to prefetch the user profiles in the queryset:
def get_queryset(self)
return Account.objects.select_related("person", "person__user").all()
def get_object(self, queryset=None):
try:
return queryset.get(pk=self.kwargs['acc_id'], person__user=self.request.user)
except Account.DoesNotExist:
raise Http404
I have to say, it's sometimes difficult to get things to fit with class-based views
in my way of perfectionism, I'm here to ask more questions about the not-so-well-documented class-based views.
I spend like 5 hours learning about class-based views, lurking into the code and I got a question.
Maybe what I'm trying to do is stupid, and if so, just say that.
I will put a simple example:
class SearchFormView(FormView):
template_name = 'search/search.html'
form_class = SearchForm
def get(self, request, *args, **kwargs):
form = SearchForm(self.request.GET or None)
if form.is_valid():
self.mystuff = Stuff.objects.filter(title__icontains=form.cleaned_data['query'])[:10]
return super(SearchFormView, self).get(request, *args, **kwargs)
This is a perfect valid class (it is, right?).
You have a form, and you make a GET request with a query parameter.
Works like a charm.
But lets imagine... I validate the query input to prevent some type of attack and I see that the query is malicious so I put a validation error.
With the old functions, I have a form instance (empty) and I put data in it and validation errors if needed. I always return that instance, if empty (first request) or if it filled with errors (the case of the malicious query).
The problem is with class-based views. In my get method I work with an extra instance of SearchForm so if I put validation stuff would be there and if I call get on the father it will use the instance on "form_class" that would be empty.
So, I think that there should be a way where I use the same form always, I mean: I call the request method, I pick the form_class (not create a new form), pass the data, validate and the father will return that form with the validation stuff.
Im not sure if I explained this correctly. So in short, Im creating a copy of the form in the get but I return the father get who have another copy that will be empty, so my when I display the template, there will be no errors because the form sended is empty.
Any ideas? Thanks.
Your problem is that super(SearchFormView, self).get(request, *args, **kwargs) renders its own form and own context. It's only a 3 line view function, so you should really be overriding what you need to change its behavior.
def get(self, request, *args, **kwargs):
form = SearchForm(self.request.GET or None)
if form.is_valid():
self.mystuff = Stuff.objects.filter(title__icontains=form.cleaned_data['query'])[:10]
return self.render_to_response(self.get_context_data(form=form))
Update: alternate idea if you'd like to continue using the super call
def get(self, request, *args, **kwargs):
self.form = SearchForm(self.request.GET or None)
if self.form.is_valid():
self.mystuff = Stuff.objects.filter(title__icontains=form.cleaned_data['query'])[:10]
return super(SearchFormView, self).get(request, *args, **kwargs)
def get_form(self, form_class):
"""
Returns an instance of the form to be used in this view.
"""
return getattr(self, 'form', None) or form_class(**self.get_form_kwargs())
The problem appears to be the fact that Django class based views only populate the form kwargs if the HTTP method is POST or PUT:
class FormMixin(object):
def get_form_kwargs(self):
"""
Returns the keyword arguments for instanciating the form.
"""
kwargs = {'initial': self.get_initial()}
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
I found this a bit peculiar also, since I have on occasion used a form in a GET request (eg. a "search" form), which needed to perform some basic validation. I just override the get_form_kwargs() method on such views, to also populate the kwargs['data'] item, even when the HTTP method is GET.