Template NoReverseMatch exceptions, outside of Models - django

I am generating some Django template code on the fly, in order to display rows in tables that are not stored in
a Django database and do not have models. I know the database and I can introspect them if needed, but I don't want
to write code by hand.
For example, field PSOPRDEFN.OPRCLASS stores an optional reference to a particular row where PSCLASSDEFN.OPRID=PSOPRDEFN.OPRCLASS, essentially a foreign key relationship. If there is no relationship PSOPRDEFN.OPRCLASS has one ' ' (space character) in it.
I also have a page for a given PSCLASSDEFN row, where the url is:
url(r'^(?i)permissions/(?P<CLASSID>[A-Z0-9_&]{1,50})/$',
'pssecurity.views.psclassdefn_detail',
name="psclassdefn_detail"),
Note that the ?P CLASSID regular expression does not allow for blanks which corresponds to gets stored in the PSCLASSDEFN table - I figure it's safer to limit what the user can put in the url request.
Back to my generated template: I want to hyperlink to the relation, if it exists. I feed my home-grown template generator a json "directive" indicating what I want put into the template (thanks for the inspiration, django-tables2):
....
{
"colname": "LANGUAGE_CD"
},
{
"urlname": "security:psclassdefn_detail",
"colname": "OPRCLASS",
"kwargs": [
{
"colname": "dbr",
"accessor": "dbr"
},
{
"colname": "CLASSID",
"accessor": "inst.OPRCLASS"
}
]
},
...
Some fairly trivial code generation then results in:
<div class="row">
<div class="col-xs-6 fieldlabel" title="LANGUAGE_CD" >Language Code</div>
<div class="col-xs-6 fieldvalue text-left _fv_LANGUAGE_CD">{{inst.LANGUAGE_CD}}</div>
</div>
<div class="row">
<div class="col-xs-6 fieldlabel" title="OPRCLASS" >Primary Permission List</div>
<div class="col-xs-6 fieldvalue _fv_OPRCLASS">
{% if inst.OPRCLASS|slugify %}
{{inst.OPRCLASS}}
{% endif %}
</div>
</div>
My problem is that started getting random Template url resolution errors when displaying some of the PSOPRDEFN data. I eventually tracked it down to the blank OPRCLASS fields in some rows.
In order to avoid this I first added
{% if inst.OPRCLASS %}
<a ...></a>
{% endif %}
That didn't work because the field is not empty, it is blank (and therefore doesn't match the CLASSID regex). So, this is where I read the filter docs again and found that slugify strips out blanks and non-alpha.
{% if inst.OPRCLASS | slugify %}
<a ...></a>
{% endif %}
Works, as a workaround. The problem is that CLASSID only stores alphanum, but that's not always true for other fields. I wouldn't mind introspecting the table column definition at template generation runtime to see what to do, but I need to find an appropriate way to disable url reversal, for only some rows.
Questions. Is there a better filter, such as a |strip? I suppose I could always build my own filter.
Even better, is there a tag to selectively catch NoReverseMatch' exceptions at template generation time?
{% try NoReverseMatch %}
{{inst.OPRCLASS}}
{% endtry %}
The reason I was so verbose in my description is because this is not something that can be worked around using Models. And neither can I custom-tune the template by hand. I find Django works quite well without models in most cases, but url reversing in templates can be quite brittle when a few rows of data do not match expectations. Hardening it would be very beneficial.

You can assign the result of the url tag to a variable.
{% url 'path.to.view' arg arg2 as the_url %}
{% if the_url %}
link
{% else %}
No link
{% endif %}
This syntax does not raise an exception if reversing the view fails.

Related

How to pass a <select> in a View using Django

I was trying to make a cart system in Django and wanted to pass Size and the Quantity of product as <Select>
input in View.
My Template have :
<ul class="list-unstyled">
Select Size:
<select name="sizes">
{% for size in product.sizes.all %}
<li class="list-item list-inline-item"><option value="{{size.nameSize}}">{{size.nameSize}}</option> </li>
{% endfor %}
</select>
</ul>
This is how it looks :
But when i Submit it using the Add to Cart Button i get error:
This is the code in the view:
def add_item(request,pk):
product = get_object_or_404(Product,pk=pk)
size = request.POST['sizes']
selectsize = Size.objects.get(nameSize=size)
user = request.user
usercart = Cart.objects.get(owner=user)
newitem = CartItems.objects.create(cart = usercart,product=product,size=selectsize)
items = usercart.cartitems
return render(request,'cart.html',{'cartitems':items})
I am trying to use the name of the size from the Template and compare the size name i have in the database for that product Using:
selectsize = Size.objects.get(nameSize=size)
I was able to get size with name 36 so i wanted to pass the value 36 from the template to the variable size using post.
But i get the error mentioned which i believe is because name for the <select> is common in all the <option>.
If i can either get an alternate way to do that or solve this error both type of solutions are welcomed.
*I am not using Django Forms because i don't know how to have django form display like i am displaying my products in cart and on the product page.
ANSWER
I was missing a submit button and was rather using a <a href="{% url 'add_item' product.pk %}>Add To Cart</a>" to submit the form which was not working.
Now i replaced it with <button class="btn btn-success" style="margin-top: 10px;" type="submit">Add To Cart New</button>
And the form Action is given the link i was trying to go to.
<form method="post" enctype="multipart/form-data" action="{% url 'add_item' product.pk %}">
A silly mistake on my side.
Thanks for the answers.
Expanding on what #Yevhenii M. said, and talking particularly about the MultiValueDictKeyError:
This error happens when the given key (sizes in this case) is not found in the POST dict. This might be happening (i'm only guessing, since you didn't post the full html code), because you didn't put the corresponding <form> tag surrounding the select.
So, the final code would look something like:
<form action="url-to-send-form-data" method="POST">
{% csrf_token %}
<select name="sizes">
{% for size in product.sizes.all %}
<option value="{{size.nameSize}}">{{size.nameSize}}</option>
{% endfor %}
</select>
</form>
The {% csrf_token %} is needed in order to protect you against Cross Site Request Forgery attacks (more info: https://docs.djangoproject.com/en/2.2/ref/csrf/)
EDIT: Now that I take a closer look, the error message shows that the url is being called with a GET request (maybe because of trying to access to /item_added/1 straight from the browser's url). That is why django can't find the sizes key.
One common way to call the url via post, is as shown in the code snipet above, and adding a submit button to the html:
...
<button type="submit">Submit</button>
</form>
You don't need to use <ul> tag here.
You can write:
Select Size:
<select name="sizes">
{% for size in product.sizes.all %}
<option value="{{size.nameSize}}">{{size.nameSize}}</option>
{% endfor %}
</select>
and result will be the same.
Since you didn't specify that add_item(request, pk) works only by POST, then you can't expect that request.POST always will be presented.
Better write your code as this:
if request.POST:
# do something
And if you don't specify default value for your select in template, then sizeswill not be in your request.POST.
You can write like this just to be sure that you got some value:
request.POST.get('sizes', 'some_default_value')
Just because you get MultiValueDictKeyError you need to see what you get in request. Maybe you get QueryDict, then you need to extract first value. For example, see this SO question. For example, print your request.POST or check type.

Repeating HTML blocks in Django

Not sure what technical term it is I'm looking for, but I have a set of HTML elements that are repeated and wondering if there is an easy way to do this.
Very simplified HTML, if I have the following:
<div class='container'>
{{ django.dataFromORM }}
</div>
I need to add to base.html in a certain section
<div id='main-container'>
all elements go here
</div>
So on run, I want to add the generated HTML the main-container. I've done this before by building in JS, but wondering if there is a way to smoothly do this in Django?
I looked at templates and partials, but not sure that's the proper way or not?
You can use include in template to include your repeated html file.
ie
<div id='main-container'>
{% include "container.html" %}
</div>
if you want to repeat it several times you can add it inside a for loop
eg:
{% for element in elements %}
{% include "container.html" %}
{% endfor %}

Detecting a URL in a Django wizard_form.html template

I have three SurveyWizardViews all of which use the same standard wizard_form.html which is located at templates/formtools/wizard/wizard_form.html as per the documentation
I have added some basic logic to this template which is designed to detect which page of the form the user is on so that I can include a non standard page/step, this is an image with a JS slider bar underneath. This all works perfectly.
{% if wizard.steps.current == '6' %}
<img src="{% static "survey/images/pathtwo/" %}{{display_image}}"/>
<section>
<span class="tooltip"></span>
<div id="slider"></div>
<span class="volume"></span>
</section>
{% endif %}
However I now want to have a slightly different experience for the user depending on which View/URL they are coming from.
Question Is it possible to detect which URL the view is currently using to look at the page? e.g.
{% if URL.current == 'www.mywebsite.com/experiment/surveyone/' %}
do X
{% if URL.current == 'www.mywebsite.com/experiment/surveytwo/' %}
do y
I have done some searching but Im not even sure what I'm searching for to be honest. Any help would be much appreciated.
You can use the request context variable. Something like:
{% if 'experiment/surveyone' in request.path %}
do this
{% endif %}
I prefer using in instead of == to ignore trailing and leading slashes. If you want the whole thing try the build_absolute_uri method. Also check what options does request offer to you (https://docs.djangoproject.com/en/dev/ref/request-response/#httprequest-objects).
Finally, don't forget to add django.core.context_processors.request to your TEMPLATE_CONTEXT_PROCESSORS (I think it is added by default).

Django templates - Formatting strings

I've these variables:
#Int
user.id
#Float (e.g. X.YYYY)
profile.rating
I need these formatted like so (the ` is delimiter):
`profile.rating`
I've tried numerous ways to format them, but none worked. For example, concatenating them: ""|add:profile.rating|add:"" gave me nothing (literally, nothing).
I suspect that this is because add: is numbers-first, but converting the numbers into string with either slugify or stringformat:"" gave me, again, nothing.
How do I do this?
Do note: I need to do this with filters since the result will be passed as a parameter to an include.
Update:
Basically, I'm building a sort of modular include. It include looks like this:
<section>
...
{% if custom_section %}
<section id="{{ custom_section_id }}">
{{ custom_section }}
</section>
{% endif %}
...
</section>
which means that I can't just directly include the values in a parameter, I need the markup that will go inside the nested section.
I managed to solve this issue with this piece of markup:
{% with "<a href="\"/user/" as link_start %}
{% with profile.rating|stringformat:".1f" as rating %}
{% with user.id|slugify as id %}
{% with link_start|add:id|add:"/\">" as link %}
{% with link|add:rating|add:"/5</a>" as data %}
{% include "XX" with custom_data:data|safe %}
{% endwith %} a couple of times
Key here is the |stringformat:".1f" and the user.id|slugify since without them, djangos worthless templating language defaults on the belief all values are numerical, and thus crap comes out.
Of note is the |safe as well, as without it the language escapes the value.
Do note: I need to do this with filters since the result will be
passed as a parameter to an include.
You can pass them directly to include, as it will take context correctly.
{% include user.id %}

Django template lists not iterable?

Running into a strange problem with Django's template, and being n00b I don't even know how to debug a template...
PROBLEM: variables of a type list somehow stopped being a list when passed into the template.
In my view, I have a bunch of variables passed to the template that are a dictionary of lists. Here's the code,
VIEW
project_image_design = {}
for p in projects:
project_image_design[p.id] = []
images = UploadedImage.objects.filter(project=p, image_type=UploadedImage.DESIGN)
for i in images:
project_image_design[p.id].append(i)
Here's the context. I have projects, each contains images. I created a dictionary, where the keys are the project id, and the value is a list of images associated with that project.
However, when I use this in the template, things go wrong,
TEMPLATE
{% for p in projects %}
<div class="row">
{% for list in project_image_design|get_item:p.id %}
{% for i in list %}
<div class="col-md-2"><img src = "{% get_static_prefix %}media/{{ i.filename }}"></div>
{% endfor %}
{% endfor %}
</div>
{% endfor %}
So in the template, I'm iterating through projects, then using the project's id (p.id) to get the dictionary value, which is a list of images, and then iterating through that. The fancy get_item tag is just a way to access dictionary values through keys that not straight-forward variables (see: Django template how to look up a dictionary value with a variable).
Anyway, I get this error:
TypeError at /designer/my_projects/
'UploadedImage' object is not iterable
The error occurs on this line: {% for i in list %}, which is the line where I'm iterating through the list of images I retrieved using my project's id.
What's going on here????
I double-checked via pdb in the view, it all checks out. The variable being passed is indeed a dictionary of lists, I put a type on all the individual dictionary elements and they're all lists (like: type(project_image_design[1]) would return <class 'list'>).
Also, at the moment, all the lists are of length 1. I'm wondering maybe the template sort of deflates lists that are size 1? That'd seem like a pretty weird thing to do, probably not the reason.
Any help would be appreciated.
Also, how do I debug templates the way I can debug Python code? Like stepping through and stuff? Is that even possible?
It hasn't stopped being a list. But you have two nested for loops: you iterate through the items you get from the dictionary - confusingly calling each item list - and then attempt to iterate again through items in that "list". But the inner loop makes no sense: you should be doing simply:
{% for i in project_image_design|get_item:p.id %}
<div class="col-md-2"><img src = "{% get_static_prefix %}media/{{ i.filename }}"></div>
{% endfor %}
I'd also point out that your view logic is over-complicated. It could be reduced to simply this:
for p in projects:
project_image_design[p.id] = UploadedImage.objects.filter(project=p, image_type=UploadedImage.DESIGN)
And in fact it could be simplified even further: you don't need the dictionary, or the get_item tag, at all. Instead, provide a method on Project called something like design_images which just returns the images of that type:
def design_images(self):
return self.uploadedimage_set.filter(image_type=UploadedImage.DESIGN)
and removing the dictionary logic from the view altogether, and now your template can just be:
{% for p in projects %}
<div class="row">
{% for i in p.design_images %}
<div class="col-md-2"><img src = "{% get_static_prefix %}media/{{ i.filename }}"></div>
{% endfor %}
</div>
{% endfor %}