I have a todo APP and I would like to have a link where the user can click and set the "todo" as complete without deleting it from my database.
I use CBV but cannot figure out how do it :
I tried
views.py :
class TodoDeleteView(LoginRequiredMixin, DeleteView):
model = Todo
success_url = '/'
template_name = 'dashboard/dashboard_confirm_delete.html'
def completeTodo(request, todo_id):
todo = Todo.objects.get(pk=todo_id)
todo.complete = True
todo.save()
But it delete it from my DB and it does not set it to true.
My models.py
class Todo(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE,verbose_name="Nom de l'utilisateur")
text = models.CharField(max_length=150, verbose_name="Nom de la Todo")
complete = models.BooleanField(default=False)
You define a DeleteView, and deleting the object, is just part of the delete control flow. In order to change the behavior, we can override the delete function, like:
class TodoDeleteView(LoginRequiredMixin, DeleteView):
model = Todo
pk_url_kwarg = 'todo_id'
success_url = '/'
template_name = 'dashboard/dashboard_confirm_delete.html'
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
success_url = self.get_success_url()
self.object.complete = True
self.object.save()
return HttpResponseRedirect(success_url)
The pk_url_kwarg is necessary to use this to obtain the primary key to filter and retrieve the correct object with get_object().
The reason why we might want to use a DeleteView, is because people can make a DELETE request on that endpoint.
Related
Relevant FormView:
class addrecipe(FormView):
form_class = AddRecipeForm
model = Recipe
template_name = 'recipebook/addrecipe.html'
fields = '__all__'
extra_context = {
'recipe_list': Recipe.objects.all()
}
Relevant Form:
class AddRecipeForm(forms.ModelForm):
name = forms.CharField(max_length="50", label="Recipe Name")
description = forms.Textarea(attrs={'class': 'desc-text-area'})
servings = forms.IntegerField()
tools = forms.ModelMultipleChoiceField(queryset=Tool.objects.all(), widget=forms.CheckboxSelectMultiple, required = True, help_text="Select all relevant tools")
class Meta:
model = Recipe
fields = ("__all__")
URL pattern for the details view page:
path('<int:pk>/recipedetails', views.recipedetails.as_view(), name='recipe_details'),
I want to have the user submit the form, then be taken to the details page of the entry they just made into the database. I've tried doing this using reverse/reverse_lazy with a success url but that hasn't been successful.
I also tried adding the following to my form view class:
def get_success_url(self):
test_recipe_id = self.object.id
return reverse('recipeBook:recipe_details', pk=test_recipe_id)
After also changing my path to:
re_path(r'(?P<pk>[^/]+)/recipedetails', views.recipedetails.as_view(), name='recipe_details'),
I get the following Value error:
AttributeError at /recipebook/addrecipe
'addrecipe' object has no attribute 'object'
Your solution was almost there.
You could use the get_success_url method to get the recipe ID after the model. This will allow you redirect with parameters.
class addrecipe(FormView):
form_class = AddRecipeForm
model = Recipe
template_name = 'recipebook/addrecipe.html'
fields = '__all__'
extra_context = {
'recipe_list': Recipe.objects.all()
}
#New method
def get_success_url(self):
test_recipe_id = self.object.id #gets id from created object
return reverse('recipeBook:recipe_details', pk=test_recipe_id)
Your detail url is not receiving the parameter as expected hence it needs to be reconfigured with a new regex
Old:
path('<int:pk>/recipedetails', views.recipedetails.as_view(), name='recipe_details'),
New:
from django.urls import path, re_path
re_path(r'(?P<pk>[^/]+)/recipedetails', views.recipedetails.as_view(), name='recipe_details),
I needed to use HttpResponseRedirect to redirect correctly. My view ended up looking like this:
class addrecipe(FormView):
form_class = AddRecipeForm
model = Recipe
template_name = 'recipebook/addrecipe.html'
fields = '__all__'
extra_context = {
'recipe_list': Recipe.objects.all()
}
def form_valid(self, form):
test_recipe = form.save(commit=False)
test_recipe.save()
test_recipe_id = test_recipe.id
return HttpResponseRedirect(reverse('recipeBook:recipe_details', kwargs={'pk': test_recipe_id}))
Saving the object before grabbing the ID appears to be a necessary step as I found that the ID itself is only created when the object is created.
The reverse return wasn't working, so honestly I hail mary'd a httpresponseredirect in front and it worked. I will update the answer if I figure out why..
I am trying to use the value from the URL on CreateView
My models are like that: Categoria > Serie
I have made a URL that path('nova-serie/<categoria>', NovaSerie.as_view(), name='nova_serie'),
The URL to create a new Serie is like that: /nova-serie/3
I am trying to use form_valid but I am receiving this message:
Cannot assign "'3'": "Serie.categoria" must be a "Categoria" instance.
views.py
class NovaSerie(CreateView):
model = Serie
form_class = SerieForm
template_name = 'nova_serie.html'
success_url = reverse_lazy('home')
def form_valid(self, form):
url = self.request.path_info
parte_final_url = url.replace('/nova-serie/', '')
form.instance.categoria = parte_final_url
return super(NovaSerie).form_valid(form)
forms.py
class SerieForm(forms.ModelForm):
class Meta:
model = Serie
fields = (
'serie',
)
widgets = {
'title': forms.TextInput(), # attrs={class="title"}
}
Can anyone here give me a help?
There is no need to do string processing on the path. You can obtain the URL parameters with self.kwargs. Furthermore if you want to specify the id of the .categoria, you should set .categoria_id:
class NovaSerie(CreateView):
model = Serie
form_class = SerieForm
template_name = 'nova_serie.html'
success_url = reverse_lazy('home')
def form_valid(self, form):
form.instance.categoria_id = self.kwargs['categoria']
return super().form_valid(form)
I would furthermore advise to specify the categoria URL parameter as an int:
path('nova-serie/<int:categoria>', NovaSerie.as_view(), name='nova_serie'),
that way if the value is not an integer it will not fire the view.
Hello guys i am trying to implement some form of access control on my views. My programme is structured as such:
1 project has some users which are tied to it in the form of a foreign key. I only wish to allow those whom are involved in it to view this project. The problem however is that the PK i use to query the database for my template is in my URL , users which do not have access to the project can simply change the url query and gain access to the items they do not have access to.
I came across django's user_passes_test method decorator and thought that it is exactly what i needed to implement this user access control.
Here is some code that i have came up with:
My view:
#method_decorator(user_passes_test(project_check(id)),name ='dispatch')
class ProjectDetailView(CreateView):
##this is actually not relavant##
model = SalesNotation
fields = ['sales_notes']
exclude = ['notation_id']
template_name = 'rnd/projects.html'
context_object_name = 'form'
##this is actually not relavant##
def get_context_data(self, **kwargs):
id = self.kwargs['id']
context = super(ProjectDetailView, self).get_context_data(**kwargs)
context['projects'] = SalesProject.objects.filter(sales_project_id = id)
This is my URL:
path('projects/<int:id>/', ProjectDetailView.as_view(), name = 'rnd-projects'),
This is my project model:
class SalesProject(models.Model):
sales_project_id = models.AutoField(primary_key=True)
sales_project_name = models.CharField(max_length=100)
salesExtra = models.ManyToManyField('SalesExtra', blank=True)
Here is my extended user model which i use to keep other information:
class SalesExtra(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
user_type = models.TextField(max_length=500, choices= role)
contact = models.IntegerField()
email = models.TextField(max_length=30,default = 'your email here')
Here is the method decorator that im using:
def project_check(user,id):
return SalesProject.objects.filter(sales_project_id=id).filter(salesExtra__user=user)
However it seems that i am unable to simply pass in the PK from the url as i recieve this error when running the server:
#method_decorator(user_passes_test(project_check(id) , name='dispatch'))
TypeError: project_check() missing 1 required positional argument: 'id
Any help will be greatly appreciated!
You can't. But you can just use UserPassesTestMixin instead:
from django.contrib.auth.mixins import UserPassesTestMixin
class ProjectDetailView(UserPassesTestMixin, CreateView):
##this is actually not relavant##
model = SalesNotation
fields = ['sales_notes']
exclude = ['notation_id']
template_name = 'rnd/projects.html'
context_object_name = 'form'
##this is actually not relavant##
def get_context_data(self, **kwargs):
id = self.kwargs['id']
context = super(ProjectDetailView, self).get_context_data(**kwargs)
context['projects'] = SalesProject.objects.filter(sales_project_id = id)
def test_func(self):
return SalesProject.objects.filter(sales_project_id=self.kwargs["id"]).filter(salesExtra__user=self.request.user)
Note test_func here which performs check. self.kwargs["id"] will give you id.
I want to get the id or pk of a ForeignKey relationship post_comment but I've tried many different ways to catch it and i do not have any good result, please guys give me a hand in this situation
In views.py
class createComment(View):
form_class = CommentForm
template_name = "createComment.html"
def get(self, request):
form = self.form_class(None)
return render(request, self.template_name, {'form':form})
def post(self, request):
obj = self.form_class(None)
obj.title_comment = self.request.POST['title_comment']
obj.body_comment = self.request.POST['body_comment']
obj.post_comment = self.pk
obj.save()
In models.py
class Comment(models.Model):
user_comment = models.ForeignKey("auth.User")
title_comment = models.CharField(max_length=50)
body_comment = models.TextField()
timestamp_comment = models.DateTimeField(auto_now=True)
post_comment = models.ForeignKey("Post", null=True)
status_comment = models.BooleanField(default=True)
def __unicode__(self):
return unicode(self.title_comment)
def __str__(self):
return self.title_comment
You can pass a primary key in the url, and then use it in your class as one way.
kwargs.get(pk name)
You could change post to:
def post(self, request, *args, **kwargs)
You then can't just assign obj.post_comment = kwargs.get(pk) you have to actually get the object.
Post.objects.get(pk = pk)
You might want to also consider renaming fieldname_comment to just fieldname for your models fields. Seems a bit redundant to have _comment on every single field in the Comment model.
I don't know how works class based views but I can tell you that self.pk does not exist in class based view, you would try get form instance and get the I'd field from this instance...
I have this app where I can upload a file to a specific category or subcategory. It works fine but the problem I'm having is when I'm trying to display select values only for a specific user and for a specific parent category it just shows me all the values stored in the database.
views.py
class AddDocumentView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
login_url = reverse_lazy('users:login')
form_class = FileUploadForm
template_name = 'docman/forms/add-document.html'
success_url = reverse_lazy('docman:index')
success_message = 'Document was successfully added'
def form_valid(self, form):
profile = form.save(commit=False)
profile.user = self.request.user
return super(AddDocumentView, self).form_valid(form)
forms.py
class FileUploadForm(forms.ModelForm):
file = forms.FileField()
class Meta:
model = Document
exclude = ('user',)
fields = [
'file',
'slug',
'category',
]
def __init__(self, user=None, **kwargs):
super(FileUploadForm, self).__init__(**kwargs)
if user:
self.fields['category'].queryset = Category.objects.filter(user_id=user.id, parent_id=None)
I've tried the solutions to the similar questions which is how I even got this far, but it's still not filtering by the user and I can't figure out how to get it to filter by the parent id either. Any ideas to what I'm doing wrong? Any help is appreciated, and I can provide more information if needed.
-----------------SOLUTION UPDATE-----------------
Thanks #solarissmoke I was able to get the user information to the form. Then I just did the same thing to capture the parent_id from the url using kwargs.
views.py
# Override the view's get_form_kwargs method to pass the user and/or pk to the form:
def get_form_kwargs(self):
pk = self.kwargs['pk']
kwargs = super(AddDocumentView, self).get_form_kwargs()
kwargs['user'] = self.request.user
# Check if category exists with pk, otherwise none
if Category.objects.filter(parent_id=pk):
kwargs['pk'] = pk
else:
kwargs['pk'] = None
return kwargs
Then I added the extra agument(pk) to init
forms.py
def __init__(self, user=None, pk=None, **kwargs):
super(FileUploadForm, self).__init__(**kwargs)
if user:
self.fields['category'].queryset = Category.objects.filter(user=user, parent_id=pk)
Your form is expecting a user argument, but you aren't supplying one, so user is always None. You can override the view's get_form_kwargs method to pass the user to the form:
class AddDocumentView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
def get_form_kwargs(self):
kwargs = super(AddDocumentView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
Your FileUploadForm will now get the user object and will filter results accordingly.