Django admin, how to check properly user's permission in django template? - permissions

Newbie here. My application name is ccad. And the model name is logbook. User A has no permission to edit, add or delete the logbook model from an available user permission.
So I tried hiding the save, save and continue editing, save and add another buttons from User A.
I followed the advised I found in SO.
Here's from picomon inquiry that was answered by Sid. And daniel the same inquiry.
I ended up writing the code below to my template.
change_form.html located at {{template folder}}/admin/app_name/model_name/
{% if perms.ccad.add_logbook %}
<li><input type="submit" value="{% trans 'Save ' %}" class="grp-button grp-default" name="_save" {{ onclick_attrib }}/></li>
<li><input type="submit" value="{% trans 'Save and add another' %}" class="grp-button" name="_addanother" {{ onclick_attrib }} /></li>
<li><input type="submit" value="{% trans 'Save and continue editing' %}" class="grp-button" name="_continue" {{ onclick_attrib }}/></li>
{% endif %}
But the user with no permission can still see the buttons I mention.
I also try changing {% if perms.ccad.add_logbook %} to {% if perms.ccad.can_add_logbook %} with no avail.
What's best way to do this?

Start with checking the perms variable in the template context. Add a ...{{ perms }}... somewhere visible to the template. It should render like this ...<django.contrib.auth.context_processors.PermWrapper object at X>....
If this is not the case you are missing the permissions in the template.
Verify that your settings TEMPLATE_CONTEXT_PROCESSORS tuple contains a django.contrib.auth.context_processors.auth.
Also make sure to use a RequestContext not a Context when rendering the template.
If you finally see a PermWrapper but your permission check still doesn't work change the previous debug to ...{{ perms.ccad }}....
This should output something similar to "set([u'ccad.add_...',...]).
If not then your app might not be called ccad.
Finally before creating the if condition be sure that the permission returns something `...{{ perms.ccad.add_logbook }}...´. This should return either True or False.
Now that i am at the end i noticed that your problem is the other way around and all I wrote so far is useless. :)
add {{ user.is_superuser }} to your template. If its True the current user has superuser rights that return always True even for {{ perms.omg.can_facepalm }}

Related

How can I pass classes in to a section in liquid / shopify?

For example passing in a snippet
{% include 'icon-top', classes:'back-to-top__icon' %}
I can pass the class back-to-top__icon into icon-top snippet
<svg class="icon {{ classes }}" somesvg stuff here ></svg>
Doing the same with a section doesn't work - is there any way to do this in liquid?
Sections doesn't accept anything outside of the section file. You can look the section like a closed platform nothing comes inside or outside of the section.
The means that variables created outside/inside the section are not accessible inside/outside of it.
That said you can hack it slightly to achieve what you want.
For example:
The section file:
test.section.liquid
The section file code:
<div class="{{dummy_class}}"></div>
Then you call the section this way:
<div style="display: none;">
{% section 'test.section' %}
</div>
{% capture section_capture %}
{% section 'test.section' %}
{% endcapture %}
{{ section_capture | replace: '{{dummy_class}}', 'back-to-top__icon' }}
Clarification
You might be asking why are we calling the section two times?
When we call the section in a {% capture %} tag it's not shown in the admin panel that's why are showing it in a hidden div only to show it in the admin and we don't do anything else with it.
After that we capture the section in a variable section_capture, this will return the content of section and we can replace anything we want in there.
That's why we added this {{dummy_class}} dummy variable. It's wrapped in liquid but you can treat it as text and not liquid, so we can write it like so #dummy_class# as well.
After that we just target that string and replace it {{ section_capture | replace: '{{dummy_class}}', 'back-to-top__icon' }}

How to render the user profile avatar image into the wagtail template?

I am creating a blog site using the Wagtail CMS. I would like to display the Author avatar image whenever a new post is published. I am trying to render the image from this /admin/account/change_avatar/ location. I can see the image uploaded here is under the wagtailusers_userprofile -> col name: avatar table, but not sure how to render it in the template.
This image isn't a typical Wagtail Image (one that comes from wagtailimages.Image), this looks like a regular models.ImageField.
Here's what's in the UserProfile model for the avatar:
class UserProfile(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='wagtail_userprofile'
)
avatar = models.ImageField(
verbose_name=_('profile picture'),
upload_to=upload_avatar_to,
blank=True,
)
Because this is a regular image field, you can get the url by appending .url in your template.
Here's some example template code:
{% if request.user.is_authenticated %}
{% if request.user.wagtail_userprofile.avatar %}
<img src="{{ request.user.wagtail_userprofile.avatar.url }}" alt="{{ request.user.get_full_name }}">
{% else %}
{# No image #}
{% endif %}
{% endif %}
The above code will check to see if the user is authenticated in the template. If you don't need it, ditch it.
THen there's the if statement to checks of the request.user.wagtail_userprofile.avatar exists. The wagtail_userprofile comes from the user field on the UserProfile model. It's using a related_name, so we use that in the template.
I also sprinkled in a {{ request.user.get_full_name }} for the alt tag, because the image alt should probably be the users name in this case, rather than the file name.
If you need the height or width, those are both available through the {{ request.user.wagtail_userprofile.avatar.height }} and {{ request.user.wagtail_userprofile.avatar.width }}.

Shopify - Get the active linklist

I'm trying to fix the breadcrumb issue for a client, shopify doesnt support nested breadcrumbs for 'n' level collections OR breadcrumbs from a page to a collection.
What I need:
Home > Page > Collection (Home > FoodDropPage > Food)
I don't really want to go the 'Hacky' tag products route as I think it puts some ownest on the users (Store owner) and I would prefer they not have to worry about it.
My current plan is to grab the active linklist, if it equals something, display its title link.
Is there a way to grab the ACTIVE linklist? I havent had much luck with the documentation and am not sure if this is possible.
If theres another solution, I would also welcome that.
Thanks!
You can use:
{% for link in linklists.main-menu.links %}
{% if linklists[link.handle] == empty %}
<li>
<a href="{{ link.url }}" class="{% if link.active %} current{% endif %}">
<span>{{ link.title }}</span></a>
</li>
{% endfor %}

Way to store data in shopify

I have a requirement, where in I have 4 different drop down on shopify home page. The First drop-down, let's name it city-drop-down, will show list of city. Based on the city selected in city-drop-down, the second drop down, lets name it category-drop-down, will show list of categories available for particular city. Similarly the third drop down should show the value based on the 2nd drop down and 4th drop down should show the value based on 3rd drop down.
Basically, I need to store list of categories available for each city. Similarly I have to store values available for each categories. How can I store this value, so that the moment a value is selected on webpage, I can use a AJAX call to get the available data for next drop down.
Edited *****
Do let me know, if I am doing it totally wrong.
Included the scripts. Please note, initially I uploaded the files under "Files". However I moved it to Assets folder as it was easier to edit the file in Assets folder.
function readcityfile(){
var xmlhttp = new XMLHttpRequest();
var url = "/assets/city_type.txt";
alert("hi");
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
try{
var myArr = JSON.parse(xmlhttp.responseText);
alert(myArr);
//myFunction(myArr);
}
catch(err) {
alert(err.message);
}
}
};
xmlhttp.open("GET", url, true);
xmlhttp.send();
}
function myFunction(arr) {
var i;
for(i = 0; i < arr.length; i++) {
alert(arr[i].city);
}
}
And the JSON file is -
[{"city": "Jamshedpur","types": "Sweets#Savories#Cake"},{"city": "Ranchi","types": "Sweets#Savories#Cake"}]
One simple way if the data size isn't too large would be to generate your data as a JSON file and simply store it as a file and then edit your theme to include the file's url. A file that is too large might be 100k. Smaller is better but if you don't have a back end to handle the AJAX calls the static file certainly provides a low cost proof of concept.
There are two ways to do this.Either as an asset or a file. Assets are part of your theme so even though you'll be altering your templates to manage this I'd tend to go with a file. (Assets are just files located under the theme but the are dealt with slightly differently)
go to your Shopify Admin control panel
Click Settings
Click Files
Click "Upload Files"
After upload you'll have a file. The next step uses the file's name not its URL.
Go to your theme editor:
Shopify Admin control panel
Online Store
Themes
click Customize Theme
drop-down Theme Options and select HTML/CSS
I'm guessing you are going to select the template product.liquid to edit.
do that and decide where you want to introduce your javascript file. If your script file was named cities_etc.js you'd import it as below:
{{ 'cities_etc.js' | file_url | script_tag}}
This method seems a bit slow if all that you are trying to do is create a tiered menu. Using Ajax requests will mean there are several round trips and it will be visually slow for the user waiting for the ajax request to complete.
You can create a linklist
I know you have already found your method but I would strongly urge you to give this a go. Here is an example of some liquid markup that will created a tiered menu. The parent linklists handle is main-menu then you need to create a linklist for each of the children where the handle matches the title in the main-menu. For example if you have an 'About Us' link in the main menu create a linklist also with the handle 'about-us'. Then just use some simple css or javascript to hide and show the menus on hover.
{% for link in linklists.main-menu.links %}
{% assign child_list_handle = link.title | handleize %}
{% if linklists[child_list_handle].links != blank %}
<li class="dropdown" aria-haspopup="true" data-id="{{ child_list_handle}}">
<a href="{{ link.url }}" class="nav-link {% if link.active %} active{% endif %}">
{{ link.title }}
</a>
<ul class="dropdown_menu hidden" id="{{ child_list_handle }}">
{% for childlink in linklists[child_list_handle].links %}
<li>
{{ childlink.title | escape }}
</li>
{% endfor %}
</ul>
</li>
{% else %}
<li>
{{ link.title }}
</li>
{% endif %}
{% endfor %}

Django custom template tag using session

I have a UpgradeView with the same possibility for saving like in the django admin. I can save, save and continue editing or save and create a new object.
Each leading to a different view:
DetailView, UpdateView and the CreateView.
After saving I want to give a message out, on every view or template its leading to.
For example "Successfully saved" or "Object could not be saved."
When writing custom template tags it's getting really hard for me, because after I created the tag, I don't know how, where and when to pass the message to the other views.
This is the UpdateView where i come from.
class TopicEditView(UpdateView):
fields = ['title','description',]
model = Topic
...
def get_success_url(self):
if self.request.POST.get('save'):
return reverse('topic_detail', kwargs={'pk':self.object.pk})
elif self.request.POST.get('save_and_continue'):
return reverse('topic_edit', kwargs={'pk':self.object.pk})
elif self.request.POST.get('save_and_create_new'):
return reverse('topic_create')
else:
return reverse('fallback_success_url')
My custom template tag is still empty, because the only examples i saw are pretty hard to understand for me.
#register.inclusion_tag('msg.html', takes_context=True)
def get_msg(context):
return None
Inside 'msg.html' i only have the string saying "Successfully saved", this did lead to nothing and i forgot why i did that.
And this is in my template (nothing):
{% load msg_handler %}
{% get_msg %}
How and where can I pass the message to these views using the session?
Try the Django messages framework instead:
http://docs.djangoproject.com/en/1.8/ref/contrib/messages
I got this faster done than I ever thought.
This is my custom template tag:
#register.simple_tag(takes_context=True)
def get_msg(context):
try:
return context.request.session.pop('msg')
except KeyError:
return ''
And this is my view, passing the message:
class TopicEditView(UpdateView):
...
def get_success_url(self):
self.request.session['msg']='Successfully saved!'
...
Nothing have changed in my template.
If there is a more elegant/useful way, i would appreciate it.
EDIT :
Thanks to Lorenzo Peña's comment, i tried using the messages framework which was really easy to use!
First i went to my views.py again and imported messages
from django.contrib import messages
and changed this line
self.request.session['msg']='Successfully saved!'
To this:
messages.add_message(self.request, messages.SUCCESS, 'Successfully saved!')
Then i made a new template called msg_loader.html containing this:
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
And changed in my other templates this
{% load msg_handler %}
{% get_msg %}
to this
{% include "msg_loader.html" %}