Is it possible to define the success_url in django without kwargs - django

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

Related

Button in the base template to switch between locations

I have a base.html template which lets me choose between shops- I want to use this setting to save a product in relationship to it's location.
I already implemented a middleware that gives me a list of all shops for a logged in user and the current shop (is this a smart way?):
from .models import Shop
class ActiveShopMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
shops = Shop.objects.filter(user=request.user.id)
request.current_shop = shops[0]
request.shops = shops
response = self.get_response(request)
return response
I create products using a form, and I want to add the current shop information to that. I do this by handing initial data to the form:
def get_initial(self):
return {"shop": self.request.current_shop.id
I want to be able to switch between shops like in the screenshot:
I tried this in base template:
{% if user.is_authenticated %}
{% if request.num_shops > 1 %}
<div class="dropdown" aria-labelledby="userMenu">
<button class="btn btn-primary dropdown-toggle mr-2" type="button" id="dropdownMenu0" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">shop: {{ request.current_shop }}</button>
<form method="post" action="">
{% csrf_token %}
<div class="dropdown-menu" aria-labelledby="dropdownMenu1">
{% for shop in request.shops %}
<input type="hidden" name="tessst" value="{{ shop.id }}">
<a class="dropdown-item" href="/">{{ shop }}</a>
{% endfor %}
</form>
</div>
</div>
{% endif %}
I want to get the shop_id variable in my index view:
def index(request):
shop_id = request.POST["tessst"]
Is this an ok approach? I wanted to avoid having an additional parameter in my urls.
i think it's better that you generate this shops datas in form . i mean you send this data to front from django fom class.
e.g:
. you can create custom form field and then every form generate this shop field for user. and then in your view you can handle this data .
. also you can create custom model field but it depends on your project .

NoReverseMatch Django: can't find pattern

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}))

How can I delete a single file in Django?

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>

Why is not there a reusable template for Django's DetailView?

Displaying forms in a template is rather easy in Django:
<form action="" method="post">{% csrf_token %}
{{ form }}
<input type="submit" value="Update" />
</form>
It is basically just one word - display the {{ form }}. It is so simple that you can use the same template for different forms.
You can limit the fields to be shown on the form using the fields = [] list if you are using CBV's such as CreateView or UpdateView.
Drawing parallel to this, one expects to have a similar workflow for showing the models as well (as opposed to editing) such as in DetailView. But, there is no such thing.. You have to write a custom template for every DetailView that you use. Such as:
<h3>User: {{ user }}</h3>
<label>First Name</label>: {{ user.first_name }} <br />
<label>Last Name</label>: {{ user.last_name }} <br />
<label>Username</label>: {{ user.username }} <br />
<label>School</label>: {{ user.person.school.name }} <br />
This is very similar to what the {{ form }} would generate, except for the field values printed here, as opposed toinputs being printed there.
So, I wonder, why isn't there a reusable generic template for DetailView's? Is there a technical limitation for this, or is it just not as reusable as I imagine?
I have created and have been using gladly for about a year now my own generic templates. So, I wanted to share, here it is:
Creating a view is as simple as this:
class PersonDetail(DetailViewParent):
model=Person
DetailViewParent used above (override fields and exclude as needed; default is to include all):
class DetailViewParent(DetailView):
fields=[]
exclude=[]
template_name='common/modal_detail.html'
def get_context_data(self, **kwargs):
context=super(DetailViewParent, self).get_context_data(**kwargs)
context['exclude']=self.exclude
context['fields']=self.fields
return context
Relevant part of the template:
{% fields %}
{% for name, label, value, is_link in fields %}
<tr>
<td><strong>{{ label|capfirst }}</strong></td>
<td>
{% if value.get_absolute_url and request.is_ajax %}
<a class="modal-loader" href="{{ value.get_absolute_url }}">{{ value }}</a>
{% elif value.get_absolute_url %}
{{ value }}
{% else %}
{% if is_link and request.is_ajax %}
<a class="modal-loader" href="{{ value }}">{{ value }}</a>
{% elif is_link %}
{{ value }}
{% else %}
{{ value }}
{% endif %}
{% endif %}
</td>
</tr>
{% endfor %}
And the template tags:
#register.tag(name="fields")
def generate_fields(parser, token):
"""
{% fields %} - loads field name, label, value, is_link to the context
"""
args=token.contents.split()
object_name='object'
if len(args) == 2:
object_name=args[1]
return FieldsNode(object_name)
class FieldsNode(template.Node):
"""
called by generate_fields above
"""
def __init__(self, object_name):
self.object_name=object_name
def render(self, context):
# Get the data necessary for rendering the thing, and add it to the context.
try:
obj=template.Variable(self.object_name).resolve(context)
except template.VariableDoesNotExist:
return ''
include_fields=context.get("fields", None)
exclude_fields=context.get("exclude", None)
fields=[]
for field in obj._meta.fields:
name=field.name
if exclude_fields and name in exclude_fields:
continue
if include_fields and name not in include_fields:
continue
label=field.verbose_name
value=getattr(obj, field.name)
is_link=(type(field).__name__ in ('URLField',))
if isinstance(value, bool):
value=get_bool_check_mark(value)
elif value is None:
value=''
fields.append((
name, label, value, is_link,
))
# If include_fields was defined, then sort by the order.
if include_fields:
fields=sorted(fields, key=lambda field_: include_fields.index(field_[0]))
context['fields']=fields
return ''
The template might be customized to your needs and liking. But I would like to note two things:
1) get_absolute_url: if this (standard django) model method is defined, the field value is shown as url.
2) modal-loader class: this triggers js on the client side to show the detail view in a bootstrap 3 modal. Furthermore, if clicked on a link as mentioned in 1) that is loaded onto the same modal, thus making it easier to browse detail views. It has also a "back" button to go back to the previous model's view. I am not including that here because it is a lot of code, and beyond the scope of this question.
I think it is not as reusable as you imagine.
It might conceivably be possible to define "standard" ways to render simple model properties like CharField - this quickly becomes impossible when you get into more complex relational fields like ManyToManyField, ForeignKey, OneToOneField. You would end up overriding any default representation very quickly for anything but the simplest of models.
Secondly Django is not - and should not be - opinionated about what your models are for, and therefore it makes sense that it doesn't try to assume how you want to render them.
This is different from forms where the structure of individual form fields is defined in Django and in HTML, and there is a strong correlation between the two.

Adding in a custom checkbox to a django form

I'm building a tool that performs some basic checking when a user goes to submit a new object via a standard form.
If an item exists with a similar name, the form is rejected and the user is asked to review the existing objects and confirm that they want to make a new one.
Now the warning checkbox below only needs to show up if there are already existing objects and the value is never stored. So its not part of the model, so I've added it into the form template below:
<form action="/createItem/" method="post">
{% if similarObjects %}
<div class="message warning">
<strong>The following public objects were found with a title similar to "<em>{{form.name.value}}</em>".
</strong>
<ul>
{% for obj in similarObjects %}
<li>{{ obj.name }}</li>
{% endfor %}
</ul>
<input type="checkbox" name="userSwearsTheyKnowWhatTheyAreDoing"/>
<label for="userSwearsTheyKnowWhatTheyAreDoing">
I've reviewed these items, and none of them meet my needs. Make me a new one.
</label>
</div>
{% endif %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
Now, whenever I try and access form.userSwearsTheyKnowWhatTheyAreDoing it gives an error.
if len(similar) == 0 or form.cleaned_data['userSwearsTheyKnowWhatTheyAreDoing']:
newObj = MyObject(**form.cleaned_data)
newObj.save()
messages.success(request,"New Object Saved")
return HttpResponseRedirect('/object/%d'% newObj.pk) # Redirect after POST
And get the error:
KeyError at /create/objectclass/
'userSwearsTheyKnowWhatTheyAreDoing'
What am I doing wrong?
You need to add a field to a ModelForm, then you have in forms.py:
class MyForm(forms.ModelForm):
userSwearsTheyKnowWhatTheyAreDoing = forms.BooleanField()
class Meta:
model = MyObject
and in your views.py:
...
myform = MyForm(request.POST)
if len(similar) == 0 or myform.cleaned_data['userSwearsTheyKnowWhatTheyAreDoing']:
newObj = myforms.save()
messages.success(request,"New Object Saved")
return HttpResponseRedirect('/object/%d'% newObj.pk) # Redirect after POST
...
The Django form does not get created and defined by whatever you are doing in the template. Instead you have to define your form with pyton by doing something like
class MyForm(forms.Form):
userSwearsTheyKnowWhatTheyAreDoing = forms.BooleanField()
...
So at lease you need to add it there. That is exaclty what the error message is telling you.