Django admin Many2Many widget customization - django

I need to customize how the m2m widget for Django Admin gets displayed but I am kind of stumped where to start. I have tried subclassing couple of widgets from django.forms and django.contrib.admin.wigets but nothing seems to be working.
Here's a depiction of what I am looking for http://i.stack.imgur.com/81AY3.png.
Any help appreciated.

That looks like the kind of thing that could be achieved with JavaScript alone. For adding your own JavaScript to the Django admin, see the documentation for ModelAdmin media definitions.

This is what I came up with. It does most of the work. However, the list does not get updated when a new item is added and changing an item does not redirect back to the original page.
/your_app/forms.py
class ProductForm(forms.ModelForm):
class Media:
js = ('js/custom_m2m.js',)
class Meta:
model = Product
/your_media/js/custom_m2m.js
django.jQuery(function() {
var $ = django.jQuery;
// Add a new place holder div to hold the m2m list
$('div.options div').append('<div class="newdiv"></div>');
// Assign some variables
var target = "options"; // <-- Target Field
var newdiv = $('div.newdiv');
var next_page = window.location.pathname;
var add_link = $('div.'+target+' div a#add_id_'+target);
var edit_img = "/static/media_admin/img/admin/icon_changelink.gif";
var add_img = "/static/media_admin/img/admin/icon_addlink.gif";
// Make the placeholder div bit nicer
newdiv.attr("style", "line-height:20px; margin-left:105px;");
// Iterate through select options and append them to 'newdiv'
$('select#id_'+target+' option[selected="selected"]').each(function() {
newdiv.append(''+$(this).text()+' <img src="'+edit_img+'" /><br />');
});
// Add a 'Add new' link after the option list
add_link.html('<strong>Add new</strong> ' + add_link.html());
add_link.appendTo(newdiv);
// Show the 'newdiv' and hide the original dropdown
$('select#id_'+target).after(newdiv);
$('select#id_'+target).css("display", "none");
$('div.'+target+' p[class="help"]').css("display", "none");
});
As you can see, the above script uses some hardcoded paths. Any improvement would be helpful.

Related

How to remember past searches for anonymous users

My application doesn't have user accounts, the users anonymously interact with it.
I want to be able to remember what a user has searched for previously without attaching it to their account (as they don't have one). How would I go about doing this?
Is cookies the answer I am looking for? If so, can you point me in the right direction.
Clarification: when the users clicks the search bar to search for something, I want to be able to display what that specific user has searched for in the past in a drop-down box.
you can use localStorage in this situation
There are code snippets which will help you (preliminarily add jQuery in you project for example in head of html
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
). Just add it in your templates
//selector by id - #selector, by class - .selector, by tag - selector
function getItemsFromLS(){
return JSON.parse(localStorage.getItem('search'))
}
function addNewItemToLS(item){
localStorage.setItem('search',JSON.stringify([...getItemsFromLS(), item]))
}
$(document).ready(function(){
//if there are no storage create it
if(!localStorage.getItem('search')){
localStorage.setItem('search', JSON.stringify([]))
}
//click on the search button
$('search-button-selector').click(function(){
//get item form input
let newItem = $('input-type-selector').val()
addNewItemToLS(newItem)
})
//show items on focus of search bar
$('input-type-selector').focus(function(){
let searchItems = []
$(this).change(function(){
searchItems = getItemsFromLS().filter((item)=>item.includes($(this).val()))
$('list-of-search').empty()
searchItems.forEach((item)=>{
$('list-of-search').append(`<span>${item}</span>`)
})
})
})
})

Modifying the Wagtail publish dropdown (per app)

I'd like to reconfigure the default "Publish" menu. The default configuration is this:
I'd like to make Publish the default action, and move it to the top. I'd also like to remove Submit for Moderation, as our site has no current need for that feature.
Ideally, I'd love to be able to override the menu config on a per-app basis - we will likely have other sections of our site in the future where we want a different config.
Is this possible?
This isn't currently possible I'm afraid - the menu items are fixed in wagtailadmin/pages/create.html and edit.html.
This is possible as of Wagtail 2.4 using the register_page_action_menu_item hook, as per Yannic Hamann's answer. Additionally, Wagtail 2.7 (not released at time of writing) provides a construct_page_listing_buttons hook for modifying existing options.
You can add a new item to the action menu by registering a custom menu item with the help of wagtail hooks.
To do so create a file named wagtail_hooks.py within any of your existing Django apps.
from wagtail.core import hooks
from wagtail.admin.action_menu import ActionMenuItem
class GuacamoleMenuItem(ActionMenuItem):
label = "Guacamole"
def get_url(self, request, context):
return "https://www.youtube.com/watch?v=dNJdJIwCF_Y"
#hooks.register('register_page_action_menu_item')
def register_guacamole_menu_item():
return GuacamoleMenuItem(order=10)
Source
If you want to remove an existing menu item:
#hooks.register('construct_page_action_menu')
def remove_submit_to_moderator_option(menu_items, request, context):
menu_items[:] = [item for item in menu_items if item.name != 'action-submit']
The default button SAVE DRAFT is still hardcoded and therefore cannot be configured so easily. See here.
It seems it can't be done on server side without some monkey-patching.
However if you want it for yourself (or have access to computers of those who will publish) you can modify your browser instead.
Install Tampermonkey browser addon
Create new script with a content below
Change http://127.0.0.1:8000/admin/* to your wagtail admin panel url pattern
Save script and check admin panel
Result should look:
// ==UserScript==
// #name Wagtail: replace "Save draft" with "Publish"
// #match *://127.0.0.1:8000/admin/*
// #require https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
// ==/UserScript==
let $ = window.jQuery;
function modify() {
let draft = $("button.button-longrunning:contains('Save draft')");
let publish = $("button.button-longrunning:contains('Publish')");
if (draft.length && publish.length) {
swap(publish, draft);
}
};
function swap(a, b) {
a = $(a); b = $(b);
var tmp = $('<span>').hide();
a.before(tmp);
b.before(a);
tmp.replaceWith(b);
};
$(document).ready(function() {
setTimeout(function() {
try {
modify();
}
catch (e) {
console.error(e, e.stack);
}
}, 100);
});
Modifying the code above, these selectors works for every admin language:
let draft = $("button.button-longrunning.action-save");
let publish = $("button.button-longrunning[name='action-publish']");

django summernote create custom buttons

I'd like to add a custom button similar to this one:
$('#makeSnote').click(function(event) {
var highlight = window.getSelection(),
spn = document.createElement('span'),
range = highlight.getRangeAt(0)
spn.innerHTML = highlight;
spn.className = 'snote';
spn.style.color = 'blue';
range.deleteContents();
range.insertNode(spn);
});
JS Fiddle
http://jsfiddle.net/ypweuw1L/
I'm not sure how would I go about adding this into my Django app? I don't see anything in the django-summernote docs.
I don't know about django-summernote well. But you can create custom button and use it on toolbar with options.
for more details...
http://summernote.org/deep-dive/#custom-button

Sitecore, render Item in code with personalization in mvc

My terminology might be slightly off as I am new to Sitecore mvc. I am trying to hardcode a view rendering with a hard coded datasource (I have to do it this way for other requirements) This view rendering has placeholders that are dynamically set and then personalized.
How can I trigger the ViewRendering on an item?
I've tried ItemRendering, but it doesn't seem to be picking up the stuff set up in the PageEditor.
* To be clear, the helpful posts below have the rendering working, but we do not seem to be getting the personalized datasources. *
I believe this is what you're after:
Item item = /* think you already have this */;
// Setup
var rendering = new Sitecore.Mvc.Presentation.Rendering {
RenderingType = "Item",
Renderer = new Sitecore.Mvc.Presentation.ItemRenderer.ItemRenderer {
Item = item
}
};
rendering["DataSource"] = item.ID.ToString();
// Execution
var writer = new System.IO.StringWriter();
var args = new Sitecore.Mvc.Pipelines.Response.RenderRendering(rendering, writer);
Sitecore.Mvc.Pipelines.PipelineService.Get().RunPipeline("mvc.renderRendering", args);
// Your result:
var html = writer.ToString();
You can statically bind a View Rendering using the Rendering() method of the Sitecore HTML Helper. This also works for Controller Renderings using the same method.
#Html.Sitecore().Rendering("<rendering item id>")
This method accepts an anonymous object for passing in parameters, such as the Datasource and caching options.
#Html.Sitecore().Rendering("<rendering item id>", new { DataSource = "<datasource item id>", Cacheable = true, Cache_VaryByData = true })
In your view rendering, I am assuming you have a placeholder defined along with the appropriate placeholder settings in Sitecore (If not, feel free to comment with more detail and I will update my answer).
#Html.Sitecore().Placeholder("my-placeholder")
In this case, "my-placeholder" will be handled like any other placeholder, so you will be able to add and personalize components within it from the Page Editor.

Insert more records at once in raw_id_fields

I have a m2m relation where in my AdminForm I would use raw_id_fields instead of the filter_horizontal option. For explanation I prefer the raw_id_fields instead of the filter_horizontal option, because the records are already categorized. So in the popup-window the user has the ability to search and filter via category.
But there are two points that I can't figure out:
possibility to selecting more than one record in the popup window
showing the real names instead of the pk in the input_field
It's possible. In order to select more than one record, you need to override default dismissRelatedLookupPopup() in django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js by including your script in the Media class of your ModelAdmin or widget:
var dismissRelatedLookupPopup = (function(prev, $) {
return function(win, chosenId) {
var name = windowname_to_id(win.name);
var elem = document.getElementById(name);
// 1. you could add extra condition checking here, for example
if ($(elem).hasClass('my_raw_id_ext_cls')) { // add this class to the field
// ...logic of inserting picked items from the popup page
}
else { // default logic
prev(win, chosenId);
}
// 2. or you could copy the following part from RelatedObjectLookups.js ...
if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) {
elem.value += ',' + chosenId;
// 2. and add a return. Remember this acts globally.
return;
} else {
document.getElementById(name).value = chosenId;
}
// 3. the following line cause the popup to be closed while one item is picked.
// You could comment it out, but this would also affect the behavior of picking FK items.
win.close();
}
})(dismissRelatedLookupPopup, django.jQuery);
Django does not support this by default. There are some snippets at djangosnippets.org, you may want to take a look at them.
Finally I'm using a modified https://django-salmonella.readthedocs.org/en/latest/. I don't show the input field and show the selected records in a table.