I have a template which displays the files that a user has uploaded. I managed to make a view that allows me do delete all the files the user has uploaded, but I also would like to make it possible to delete each one individually.
I have a bootstrap card, and in the body I display each file with the delete link on the right:
<div class="card-body text-light">
{% for doc in docs %}
<ul>
<font face="Ubuntu">{{doc.document|filename}}</font>
<font face="Ubuntu" color="red">Delete</font>
</ul>
{%endfor%}
</div>
And in the card footer I use the view that deletes all files:
<div class="card-footer bg-transparent border-light">
<i class="fas fa-trash-alt" style="color:red"></i> <font face="Ubuntu" color="red"><b>Delete All Files</b></font>
</div>
My delete view is as follows:
def delete(request, **kwargs):
documents = Document.objects.filter(owner=request.user.id)
documents.delete()
cleanup_post_delete.connect(delete, request)
return redirect('main:user_panel')
The problem is, I can't figure how to delete each file individually, thought of using the objects.get() method but it really can't help me. I would need some view that targets that specific file and deletes it.
UPDATE:
So, here is how I managed the problem:
I made another view called delete_single:
def delete_single(request, id):
document = Document.objects.get(pk=id)
document.delete(id)
return redirect('main:user_panel')
But that wasn't enough, so by searching a bit I found a way around, these two classes will help in terms of security, since I found out that otherwise my file objects may be susceptible to CSRF attacks (not that would matter right now for me, since this is just a project of mine and I don't plan anything special with it, but I take it as good practice anyways):
class PermissionMixin(object):
def get_object(self, *args, **kwargs):
obj = super(PermissionMixin, self).get_object(*args, **kwargs)
if not obj.owner == self.request.user:
raise PermissionDenied()
else:
return obj
class PostDelete(PermissionMixin, DeleteView):
model = Document
success_url = reverse_lazy('main:user_panel')
Also in urls.py:
url(r'^delete_single/(?P<pk>\d+)/$', views.PostDelete.as_view(), name='delete_single')
And finally in my template:
<form action="{% url 'main:delete_single' doc.pk %}" method="post">
{% csrf_token %}
<input class="btn btn-danger" type="submit" value="Delete" />
</form>
Related
I am elaborating on the tutorial in the django docs to build a voting app. What I try to achieve is to be able to delete a candidate and, at success, get back to the detailview of the election. I know I could just add another parameter to the url like (full template below)
<a href="{% url 'candidate_delete' c.id object.id %}" class="btn btn-danger fa fa-trash" class></a>
I would like to know whether it is possible to use a post method (although there is no form). I did some research, found the 'next' parameter, but it does not get through. It looks like it needs a form, since all examples are using the 'next' within a form.
I also tried setting the success_url based on the election the to-be-deleted-candidate is ForeignKey-d to, but that generates the error:
ImproperlyConfigured at /elections/candidate/delete/13/
The included URLconf '1' does not appear to have any patterns in it. If you see valid patterns in the file then the issue is probably caused by a circular import.
This is the view:
class CandidateDelete(LoginRequiredMixin, DeleteView):
model = Candidate
template_name = 'election/delete.html'
def get_object(self):
obj = super().get_object()
print(self.request.POST)
election = Election.objects.get(id=obj.poll_id)
if not election.owner_id == self.request.user.id:
raise Http404
return obj
def get_success_url(self, **kwargs):
obj = super().get_object()
election = Election.objects.get(id=obj.poll_id)
return reverse_lazy('election_detail', election.id)
The election_detail template
{% extends 'base.html' %}
{% block content %}
{{object.name}} -
<ul>
{% for c in candidate_list %}
<h2>{{ c.name }}</h2>
<li> {{ c.intro }} {{c.id}}
{{c.email}}
<a href="{% url 'candidate_delete' c.id %}" class="btn btn-danger fa fa-trash" class></a> <input type="hidden" name="next" value={{object.id}} />
</li>
{% endfor %}
<a href="{{ request.META.HTTP_REFERER }}" class="btn btn-primary" class>Back</a>
</ul>
{% endblock %}
the object in the template is the election that the candidates are linked to.
As you can see, I tried the post method, but, reading around, it seems to only work in a form. The success_url config throws an error as well.
Any help to use the post method or configure get_success_url with data from the model is much appreciated.
So, apparently, the reverse_lazy has to look like this:
def get_success_url(self, **kwargs):
obj = super().get_object()
election = Election.objects.get(id=obj.poll_id)
return reverse_lazy('election_detail', kwargs={'pk':election.id})
While in the template, you can just add the var, in the return function you have to specify it is a kwargs.
I am almost sure the "election= .. "can be shorter, but that is for later
I am working on developing a permitting app using django. This is my first django project so bear with me here...
we have a default utility permit that contains some basic info like property owner and address. Then from that you can attach a sewer, or water or row or any combination of related tables to the permit. Basically I am looking for a way to return a page with the default utility permit then have a series of links or buttons to add more forms to that page.
I made some model forms for each of the models and can display them individually on the page
forms.py
class UtilityPermitForm(forms.ModelForm):
class Meta:
model = UtilityPermit
fields = ['...']
class SewerPermitForm(forms.ModelForm):
class Meta:
model = SewerPermit
fields = ['...']
class WaterPermitForm(forms.ModelForm):
class Meta:
model = WaterPermit
fields = ['...']
I successfully added them to a list and could iterate through and get them to add
views.py
class BuildForms(View):
permits = []
utility_form = UtilityPermitForm
sewer_form = SewerPermitForm
water_form = WaterPermitForm
permits.append(utility_form)
permits.append(sewer_form)
permits.append(water_form)
template_name = 'engineering/UtilityPermitForm2.html'
def get(self, request, *args, **kwargs):
out_permits = []
for form in self.permits:
out_permits.append(form())
return render(request, self.template_name, {'form': out_permits})
def post(self, request, *args, **kwargs):
if request.GET.get('testButton'):
return HttpResponse("I guess")
form = self.utility_form(request.POST)
return render(request, self.template_name, {'form': form})
def add_permit(self, request, permit):
# need to get a thing to add a permit to the list
pass
.html
{% block content %}
<div>
<form class="site_form" action={% url 'engineering:utility_permit' %} method="post">
{% csrf_token %}
{% for item in form %}
{{ item }}
<hr>
{% endfor %}
<input type="submit" value="Submit">
</form>
</div>
{% endblock content %}
so again, my problem is I want to start with a one permit and then have links or buttons to add each form as needed. I'm a bit at a loss here and any help would be greatly appreciated.
EDIT:
so I have this base permit that comes up when a user navigates to it like so, and I want to have a user click the add sewer permit button or link or whatever
and then the corresponding permit will come up
you can create multiple same form in one page dynamically using formset
see Documentation
and maybe this tutorial is exactly what you are looking for.
EDITED
if I understand your question correctly, how about this:
first, it would be better to separate your form with dictionaries instead of list in your views.py
context = {
'utility_form': self.utility_form,
'sewer_form': self.sewer_form,
'water_form': self.water_form
}
return render(request, self.template_name, context)
then in your .html file,
if you want to add one form each time you click the button, my trick is:
show your base permit form first (said utility_form), button to add other form, and hide your other form first.
<div class="form-container">
<form class="site_form" action={% url 'engineering:utility_permit' %} method="post">
{% csrf_token %}
{{ utility_form }}
<div id="additional-forms"></div> <!-- notice this div -->
<hr>
<input type="submit" value="Submit">
</form>
</div>
<button class="add-sewer-form">Sewer Permit</button>
<div id="sewer-form-template" style="display: none;">
<div class="sewer-form-container">
{{ sewer_form }}
</div>
</div>
and then using jquery to add onclick listener, clone that hidden form, then insert it after base form (actually inside div with id additional-forms).
$('.add-sewer-form').click(function(){
let sewer_form = $('#sewer-form-template .sewer-form-container:first').clone(true);
$(sewer_form).appendTo($('#additional-forms'))
});
I haven't test it yet, but when you click the add button, it should be give result like this:
<div class="form-container">
<form class="site_form" action={% url 'engineering:utility_permit' %} method="post">
{% csrf_token %}
{{ utility_form }}
<div id="additional-forms">
<div class="sewer-form-container">
{{ sewer_form }}
</div>
</div>
<hr>
<input type="submit" value="Submit">
</form>
</div>
<button class="add-sewer-form">Sewer Permit</button>
<div id="sewer-form-template" style="display: none;">
<div class="sewer-form-container">
{{ sewer_form }}
</div>
</div>
Hope it can answer your question :)
First add the button
<button><button>
Then add onclick attribute to it which will help react on click
<button onclick='do'><button>
Then create script that contain the function to display the other form
<script>
function do() {
document.getElementById('form').innerHTML ='add your form here'
}
</script>
all together
<button onclick='do'><button>
<script>
function do() {
document.getElementById('form').innerHTML ='add your form here'
}
</script>
The problem:
I am working on a webapp that has a 'StudentListView' that should have the following features:
Display a list of students
Have a searchbox above that allows the user to filter / search this list (Submitting 'Peter' should return all students with 'Peter' in their name)
An 'export' button should allow the user to export this (possibly filtered!) list to a .csv file
I've implemented the code below to allow the user to filter the list, which works as intended. I've also created an export_students function that creates an .csv file of all students in the supplied queryset. This function also works as intended.
However, when exporting a filtered list the program does not behave as the user expects. The user will first filter the list by providing search parameters, which will trigger an request and refresh the page. The user than presses the 'export' button, but since he did not re-submit the search parameters (why would he, the list he sees is already filtered) none are provided in the request and thus the csv file contain all students in the database, instead of the filtered selection he expects.
Possible solution
This problem would be solved if I could somehow store the search parameters of the request, and have them retrieved if the following request.GET contains the exportstudents keyword. But I am not sure how I could accomplish this. The closest I got was by using javascript to append the search params to the value of the Export button. But this meant that request.GET['exportstudents'] had the entire search query as its value (I removed most of the search/filter options in my code examples for simplicity, but these parameter strings can get really long)
I could of course parse the results with a complicated regex but seems like a very convoluted solution for a problem that probably has a much easier solution.
The code
Again, I removed most of the filter parameters for simplicity, but the code below should give an good indication of how my view functions.
The HTML appends keywords to the requests .GET parameter, which, if present, are either used to filter the queryset or trigger the export_students function.
<form method="get">
<input name="search_query" type="text" class="form-control"
value="{% if request.GET.q %}{{ request.GET.q }}{% endif %}"
placeholder="Find student"
/>
<button type="submit">Filter Students</button></span>
</form>
<form id='exportform' class="col-md-5 " method="get" action="">
<div class="input-group">
<button id='export' name="exportstudents" value="true">Export Students</button>
</div>
</form>
class StudentListView(generic.ListView)
def get_queryset(self):
field = self.request.GET.get('field', 'last_name')
qs = Student.objects.all()
search_query = self.request.GET.get('search_query', None)
if search_query:
qs = qs.filter(
Q(first_name__icontains=search_query) |
Q(last_name__icontains=search_query) |
Q(email_parents__icontains=search_query)
)
return qs
def get(self, request, *args, **kwargs):
if 'exportstudents' in self.request.GET:
qs = self.get_queryset()
file = export_students(qs)
content = 'attachment; filename="{}_{}.csv"'.format(
u'Student_export',
timezone.now().strftime('%d-%m_%H:%M')
)
response = StreamingHttpResponse(
file, content_type='text/csv')
response['Content-Disposition'] = content
return response
return super(UserListView, self).get(request, *args,
**kwargs)
The functionality that this view seeks to provide are commonplace on many websites, so obviously a solution is possible. But I am currently at a loss on how to allow users to export filtered lists when those filters have been applied to the previous queryset.
Export students is also a form. So you could add the parameter there as a hidden field - now your existing view code will just work.
<form id='exportform' class="col-md-5 " method="get" action="">
<input type="hidden" name="search_query" value="{{ request.GET.search_query }}"
<div class="input-group">
<button id='export' name="exportstudents" value="true">Export Students</button>
</div>
</form>
Alternatively, just use a single form with two buttons:
<form method="get">
<input name="search_query" type="text" class="form-control"
value="{% if request.GET.q %}{{ request.GET.q }}{% endif %}"
placeholder="Find student"
/>
<button type="submit">Filter Students</button></span>
<div class="input-group">
<button id='export' name="exportstudents" value="true">Export Students</button>
</div>
</form>
Again, this will just work (although I suspect you meant request.GET.search_query there too).
You may also be able to get access to the previous request's GET using the request.META.get('HTTP_REFERER') header on the request. But it is possible that some browsers could be configured not to send the referer.
I'm building my first application (guess app) with Django, I've been doing well so far. But I've encountered an error when trying to redirect to a Detail View when a user submits a file through a submit function (similar to a 'post blog' scenario).
I've looked up several posts with the same problem and I can not figure out why my code is not working.
views.py
#login_required
def submit(request):
if request.method == 'POST':
submited_form = SubmitFileForm(request.POST, request.FILES)
if submited_form.is_valid():
...
form.save()
return HttpResponseRedirect(reverse('result-detail'), kwargs={'pk': form.pk})
else:
submited_form = SubmitFileForm()
return render(request, 'guess/submit.html', context)
class ResultDetailView(LoginRequiredMixin, DetailView):
model = Result
template_name = 'guess/result_detail.html'
context_object_name = 'result'
I know I'm mixing class based views with function based views, but for some reason I can not get my submit function to work when I try to implement it as a class based view. Anyway, I think that shouldn't be a problem
urls.py
url_patterns = [
...
path('result/<int:pk>', guess_views.ResultDetailView.as_view(), name='result-detail'),
...
]
result_detail.html
{% extends "guess/base.html" %}
{% block content %}
<article class="media content-section">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="#">{{ result.author }}</a>
<small class="text-muted">{{ result.date_posted }}</small>
</div>
<h2 class="article-title">{{ result.title }}</h2>
<p class="article-content">{{ result.statistic }}</p>
</div>
</article>
{% endblock content %}
I'd expect a redirect to the detail view of the object that has been created with the submit function and submitfileform (model form). I can access the details if I just type /result/ and the primary key of any created object. But apparently I can't do the same through a redirect.
The error I am getting:
NoReverseMatch at /submit/
Reverse for 'result-detail' with no arguments not found. 1 pattern(s) tried: ['result/(?P<pk>[0-9]+)$']
In your code, you have two problems. First, form is not a Model instance. When you call form.save(), it will return the model instance. So you need to store it in a variable. Second problem is that, you need to pass the kwargs as a known argument in the reverse, not in HttpResponseRedirect. So the following code should work:
instance = form.save()
return HttpResponseRedirect(reverse('result-detail',kwargs={'pk': instance.pk}))
OK - I'm positive that the issue is that I have some fundamental understanding of how forms work in Django, so 30,000 ft conceptual explanations are welcome, in addition to code fixes!
I'm trying to run my site from (mostly) a single view (and single template), with modal popups to view info (working) and to edit or add it (not working). I can see the form, fill it out, and click submit, at which point the modal closes, but no new Courses show up in the admin view.
I'll confine this to a single model - my simplest - for the time being:
models.py:
class Course(models.Model):
Name = models.CharField(max_length=30,unique=True)
Active = models.BooleanField(default=True)
def __unicode__(self):
return u'%s' % (self.Name)
views.py
def IndexView(request,Course_id,Section_id):
template_name = 'gbook/index.html'
print Course_id,Section_id
this_course = Course.objects.get(pk=Course_id)
active_courses = Course.objects.all().filter(Active=True).exclude(pk=Course_id)
section_list = Section.objects.all().filter(course=this_course)
if len(section_list) >1:
multi_section = True
else:
multi_section = False
active_section = Section.objects.get(pk=Section_id)
roster = Student.objects.all().filter(sections__in=[active_section])
announcement_list = Announcement.objects.all().filter(sections__in=[active_section])
courseaddform = CourseAddForm()
context = {'active_courses':active_courses, 'this_course': this_course,
'active_section':active_section, 'section_list':section_list,
'roster':roster, 'multi_section':multi_section,
'announcement_list':announcement_list, 'courseaddform':courseaddform}
return render(request,'gbook/index.html', context)
forms.py
class CourseAddForm(forms.ModelForm):
class Meta:
model = Course
fields = ['Name', 'Active']
templates/index.html
...
<li class="dropdown">
<span class="glyphicon glyphicon-cog" aria-hidden="true"></span><span class="caret"></span>
<ul class="dropdown-menu">
<li><a data-toggle="modal" data-target="#SectionRosterModal">Roster</a></li>
<li><a data-toggle="modal" data-target="#AnnouncementModal">Announcements</a></li>
<li><a data-toggle="modal" data-target="#CourseAddModal">CourseAdd</a></li>
</ul>
</li>
...
<!-- COURSE ADD MODAL -->
<div class="modal fade" id="CourseAddModal" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header" style="padding:5px 10px;">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4>Add Course</h4>
</div>
<div class="modal-body" style="padding:10px 10px;">
<form data-parsley-validate method="post" id="courseaddform" action="" enctype="multipart/form-data"
data-parsley-trigger="focusout">
{% csrf_token %}
{{ courseaddform.as_p }}
<p id="login-error"></p>
<input type="submit" class="btn btn-info submit" name="AddCourse" value="Add Course" />
</form>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
...
I think that there's supposed to be a POST command in there somewhere, but I really don't have a good handle on the process here. Thanks for the help!
It looks like you are not doing anything with the form data when you post back, you need to process the form if the request method is a POST
def IndexView(request, ...):
if request.method == "GET":
... do what you are doing now and return
elif request.method == "POST":
cf = CourseAddForm(request.POST)
if cf.is_valid():
...do stuff with cf.cleaned_data <---- this is a dict
return ....
You are doing the same thing for GET and POST requests right now and neither deals with the submitted form
see here for more details
https://docs.djangoproject.com/en/1.10/topics/forms/#the-view
EDIT #1:
The POST should be a standard HTTP POST request back to the same URL. Just set the method tag as you are now and action="." (or a URL lookup in the template).
You need to return a valid HTTPResponse object but the normal case when dealing with a form is to return a HTTPResponseRedirect(...some url...) if the form is valid. In the case of a single page app, if you reload the same page you need to do everything you did in the request.method == "GET" so maybe return a HTTPResponseRedirect back to the same URL. In this case, I would look at the django messages framework and add a message to the context saying the form was submitted successfully and display the message in the reloaded page (you should always check if there is a message to display anyway when using the messages framework so this will not break the case where you are loading the page for the first time)
https://docs.djangoproject.com/en/1.9/ref/contrib/messages/