Django Admin: populate the field based on previous field value - django

I have a model in django admin as follows
ChoiceA= (
("on-false","on-false"),
("on-true","on-true"),
)
ChoiceB = (
("always","always"),
("never","never"),
)
id = models.CharField(verbose_name="Field",max_length=32)
type = models.CharField(verbose_name="Expression",max_length=32)
action = models.CharField(max_length=32, choices=x)
Now based on the type entered by the user ie if user enters type = "a" then action's choices should be set to ChoiceA and if user enters type ="b" then action's choices should be set to ChoiceB. How can I achieve this in Django Admin?
Edit:
action_change.js
jQuery(document).ready(function(){
$("#id_type").change( function(event) {
$.ajax({
"type" : "POST",
"url" : "/action_choices/",
"dataType" : "json",
"cache" : false,
"error" : alert("hello"),
"success" : function(json) {
$('#id_action >option').remove();
for(var j = 0; j < json.length; j++){
$('#id_action').append($('<option></option>').val(json[j][0]).html(json[j][1]));
}
}
});
});
});

You can achieve it using Ajax and jQuery:
models.py:
type = models.CharField(verbose_name="Expression",max_length=32)
action = models.CharField(max_length=32, choices = (('', ''), ))
admin.py:
class MyModelAdmin(admin.ModelAdmin):
list_display = ('type', )
class Media:
js = ['/static/js/action_change.js']
admin.site.register(MyModel, MyModelAdmin)
urls.py:
url(r'^action_choices/', 'myproject.myapp.views.action_choices'),
views.py:
def action_choices(request):
action_list = []
ChoiceA = ("on-false", "on-true")
ChoiceB = ("always", "never")
action_type = request.GET.get('action_type')
if str(action_type).lower() == 'a':
choices = ChoiceA
elif str(action_type).lower() == 'b':
choices = ChoiceB
else:
choices = ()
[action_list.append((each,each)) for each in choices]
json = simplejson.dumps(action_list)
return HttpResponse(json, mimetype='application/javascript')
Create the file action_change.js with following content in your static folder and define correct path in class Media of ModelAdmin.
action_change.js
(function($){
$(function(){
$(document).ready(function() {
$('#id_type').bind('keyup', type_change);
$('#id_action >option').show();
});
});
})(django.jQuery);
// based on the type, action will be loaded
var $ = django.jQuery.noConflict();
function type_change()
{
var action_type = $('#id_type').val();
$.ajax({
"type" : "GET",
"url" : "/action_choices/?action_type="+action_type,
"dataType" : "json",
"cache" : false,
"success" : function(json) {
$('#id_action >option').remove();
for(var j = 0; j < json.length; j++){
$('#id_action').append($('<option></option>').val(json[j][0]).html(json[j][1]));
}
}
})(jQuery);
}
This should work fine for the scenario you asked. And I'm giving my suggestion below:
models.py
type = models.CharField(verbose_name="Expression",max_length=32, choices = (('a', 'a'), ('b', 'b'), ))
action = models.CharField(max_length=32, choices = (('', ''), ))
action_change.js (line 5)
$('#id_type').bind('change', type_change);

You would have to initialize the action field with all possible choices, or Django will complain that a choice that didn't previously exist isn't a valid choice.
My recommendation would be to initialize the field with all of the possible choices, and use JavaScript to toggle the visibility of the choices, depending on the value of type. There are a few plugins around that will handle dynamic fields in Django admin, but most that I've seen deal with ForeignKey or ManyToMany fields that need to do lookups.
You're probably best off just adding some JavaScript to your admin form via the Media meta class and handling it yourself.

Related

Doctrine: Automatic ResultMapping of Native Query?

After some research, I made this UNION query work in my Repository class:
class PostRepository extends ServiceEntityRepository {
// ...
public function getLatestPostsOfUser ($limit = 10) : ?array {
$sql = <<<SQL
SELECT p.id, p.title, p.content, p.images, p.parent_id, p.parent_type, p.created, p.last_modified FROM cms_posts p
LEFT JOIN cms_user_follow ON (p.parent_type = cms_user_follow.followed_entity_type AND p.parent_id = cms_user_follow.followed_entity_id)
WHERE cms_user_follow.user_id = {$this->currentUser->getId()}
UNION
SELECT p.id, p.title, p.content, p.images, p.parent_id, p.parent_type, p.created, p.last_modified FROM cms_posts p
LEFT JOIN project_memberships ON (p.parent_type = 'Project' AND p.parent_id = project_memberships.project_id)
WHERE project_memberships.user_id = {$this->currentUser->getId()} and project_memberships.status = 1
ORDER BY created DESC
LIMIT $limit
SQL;
$res = [];
try {
$rsm = (new ResultSetMapping())
->addEntityResult(Post::class, 'p')
->addFieldResult('p', 'id', 'id')
->addFieldResult('p', 'title', 'title')
->addFieldResult('p', 'content', 'content')
->addFieldResult('p', 'images', 'images')
->addFieldResult('p', 'parent_id', 'parentId')
->addFieldResult('p', 'parent_type', 'parentType')
->addFieldResult('p', 'created', 'created')
->addFieldResult('p', 'last_modified', 'lastModified')
;
$res = $this->getEntityManager()->createNativeQuery($sql, $rsm)->getArrayResult();
} catch (DBALException $e) {
}
return $res;
}
}
It involves an awefull lot of manual field mapping, so I was wondering wheather there is an automatic solution to this?
Many thx!
It looks like Doctrine can do something like this under the hood and also apply the mappings automatically:
$qb = $this->em->createQueryBuilder();
$where = $qb->expr()->andX(
$qb->expr()->eq('f.followedEntityId', ':parentId'),
$qb->expr()->eq('f.followedEntityType', ':parentType'),
$qb->expr()->eq('f.following', 1),
$qb->expr()->eq('u.allowEmailNotifications', 1),
$qb->expr()->eq('u.enabled', 1),
);
$qb->select('f', 'u')
->from(UserFollow::class, 'f')
->leftJoin(
User::class,
'u',
Join::WITH,
'f.userId = u.id'
)
->where($where)
->setParameters([
'parentId' => $post->getParentId(),
'parentType' => $post->getParentType()
])
;
$usersAndFollowings = $qb->getQuery()->getResult();
$usersAndFollowings is then a flat array with both entities alternating: [UserFollow, User, UserFollow, User, ...]
You'll probably want to process it afterwards, so that connected UserFollow and User entities are together in a sub array.

Returning Dictionary From templatetags and Printing in template

I am trying to return most voted answer for each question.also i want to send also extra infomation of that answer like vote and id.
Printing one value is easy but for more than one i have to return dictionary.So how can i return dictionary and print all values in template.
from django import template
register = template.Library()
#register.simple_tag
def getmostvotedanswer(answers):
answer = answers.order_by('-vote')[0]
answer_info = {
'answer':answer.answer,
'vote':answer.vote,
'id':answer.id
}
return answer_info
index.html
<p class="small text-muted ">{% getmostvotedanswer question.answer_set.all %}</p>
Output
{'answer': 'THIS IS ANSWER THIS IS ANSWER THIS IS ANSWER THIS IS ANSWER THIS IS ANSWER', 'vote': 7, 'id': 1}
I can call template_tag 3 times for three values.
But I don't want to call templatetag again and again i think it will affect performance.
view.py
def index(request):
questions = Question.objects.all()
context = {
'questions':questions
}
return render(request,'index.html',context=context)
Edit -> Add view.py
The easiest, debugable, best performance and test friendly way to achieve it is to cook data on view instead of write custom template tag. Windows functions are needed to get the first answer of each question:
from django.db.models import F, Window
from django.db.models.functions.window import FirstValue
def index(request):
#q_and_a_ids = [ (id question, id most voted answer), (... ]
q_and_a_ids = (
Question
.objects
.annotate(
most_voted_id=Window(
expression=FirstValue('answer__id'),
partition_by=['id'],
order_by=F('answer__vote').desc()
)
)
.distinct()
.values_list( 'id', 'most_voted_id')
)
answers_ids = set( [ a_id for (_,a_id) in q_and_a_ids] )
questions_dict = Question.objects.in_bulk()
answers_dict = Answers.objects.filter(pk__in=answers_ids).in_bulk()
#q_and_a = [ { 'q':question, 'a':most voted answer}, { ... ]
q_and_a = [ {'q': questions_dict[q_id],
'a': answers_dict.get(a_id) }
for (q_id,a_id) in q_and_a_ids ]
context = {
'questions_and_answers': q_and_a
}
return render(request,'index.html',context=context)

Django 2 upgrade lost filter_horizontal functionality

I recently upgraded to Django 2.2.2 and Python 3.6.8, and my filter_horizontal feature in Django admin has disappeared.
I tried viewing my admin in Chrome incognito mode, as some answers suggest, and I also tried changing verbose_name strings to unicode. However, none of these worked.
Here is an example of a model for which I am attempting to show filter_horizontal. This worked on my app prior to the upgrades.
admin.py
class ResearchAdmin(admin.ModelAdmin):
filter_horizontal = ('criteria', 'state')
def save_model(self, request, obj, form, change):
obj.save()
# Update cache for tasks containing the saved goal
tasks = Task.objects.filter(research__id=obj.pk).values_list('pk', flat=True)
for t in tasks:
cache.delete_many(['task%s' % t, 'task%s_research' % t])
models.py
"""
Clinical Research model
"""
def __str__(self):
return "%s" % (self.name)
name = models.CharField(max_length=50, null=True, blank=True)
type = models.CharField(max_length=50, null=True, blank=True)
cta = models.CharField(max_length=50, null=True, blank=True)
description = models.TextField(null=True, blank=True)
picture = models.ImageField(upload_to='images/%Y/%m/%d', null=True, blank=True, help_text="Upload portrait image for modal study description")
layout = models.CharField(max_length=1, choices=LAYOUT_TYPE, null=False, blank=False, default='r')
criteria = models.ManyToManyField('Answer', blank=True, db_index=True, help_text="Answers required for qualified patients")
required_contact = models.ManyToManyField('ContactField', blank=True, db_index=True, help_text="Contact info for patient to enter")
email = models.EmailField(null=True, blank=True, help_text="Sponsor email for notifying of screened patients")
link = models.URLField(null=True, blank=True)
state = models.ManyToManyField('State', blank=True, help_text="Qualifying states")
lat = models.CharField(max_length=60, null=True, blank=True)
lng = models.CharField(max_length=60, null=True, blank=True)
distance = models.PositiveIntegerField(null=True, blank=True, help_text="Maximum distance from user in miles to show")
class Meta:
verbose_name = u"Research"
verbose_name_plural = u"Research"
There are no error messages, but the filter_horizontal frontend doesn't show up in admin for the criteria and state fields.
##collectstatic##
As #iain-shelvington suggested, this issue may be due to some kind of interference with the cached front-end code required to display the filter_horizontal format. I have tried running in Google Incognito mode, clearing catch, and running collectstatic --clear, and none of these work. Moreover, there aren't any differences between the admin static files pre- and post- upgrade.
#SylvainBiehler pointed out that django_gulp may be overriding collectstatic. I disabled django_gulp and ran ./manage.py collectstatic --clear all admin files are now updated post Django upgrade.
##Comparing admin files pre- and post- Django upgrade##
I was able to spin up a version of my app pre-Django upgrade, and the filter_horizontal capability works in the older version. There are some differences in the construction of the Criteria field from Chrome console:
Old Version (works)
Select element prior to choices:
<select multiple="multiple" class="selectfilter" id="id_criteria" name="criteria">
Javascript after choices:
<script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("id_criteria", "criteria", 0, "https://xxxxxxx/static/admin/"); });</script>
New Version (broken)
Slightly different select element. No javascript after choices:
<select name="criteria" id="id_criteria" multiple class="selectfilter" data-field-name="criteria" data-is-stacked="0">
This seems to be causing the issue, but I have no idea how to fix it. Any ideas?
##Analyzing admin JS in console##
SelectBox.js loads in the console and declares var = SelectBox = {... which is never called.
SelectFilter.js also loads but the function is never called:
/*
SelectFilter2 - Turns a multiple-select box into a filter interface.
Requires core.js, SelectBox.js and addevent.js.
*/
(function($) {
function findForm(node) {
// returns the node of the form containing the given node
if (node.tagName.toLowerCase() != 'form') {
return findForm(node.parentNode);
}
return node;
}
window.SelectFilter = {
init: function(field_id, field_name, is_stacked, admin_static_prefix) {
if (field_id.match(/__prefix__/)){
// Don't intialize on empty forms.
return;
}
var from_box = document.getElementById(field_id);
from_box.id += '_from'; // change its ID
from_box.className = 'filtered';
var ps = from_box.parentNode.getElementsByTagName('p');
for (var i=0; i<ps.length; i++) {
if (ps[i].className.indexOf("info") != -1) {
// Remove <p class="info">, because it just gets in the way.
from_box.parentNode.removeChild(ps[i]);
} else if (ps[i].className.indexOf("help") != -1) {
// Move help text up to the top so it isn't below the select
// boxes or wrapped off on the side to the right of the add
// button:
from_box.parentNode.insertBefore(ps[i], from_box.parentNode.firstChild);
}
}
// <div class="selector"> or <div class="selector stacked">
var selector_div = quickElement('div', from_box.parentNode);
selector_div.className = is_stacked ? 'selector stacked' : 'selector';
// <div class="selector-available">
var selector_available = quickElement('div', selector_div, '');
selector_available.className = 'selector-available';
var title_available = quickElement('h2', selector_available, interpolate(gettext('Available %s') + ' ', [field_name]));
quickElement('img', title_available, '', 'src', admin_static_prefix + 'img/icon-unknown.gif', 'width', '10', 'height', '10', 'class', 'help help-tooltip', 'title', interpolate(gettext('This is the list of available %s. You may choose some by selecting them in the box below and then clicking the "Choose" arrow between the two boxes.'), [field_name]));
var filter_p = quickElement('p', selector_available, '', 'id', field_id + '_filter');
filter_p.className = 'selector-filter';
var search_filter_label = quickElement('label', filter_p, '', 'for', field_id + "_input");
var search_selector_img = quickElement('img', search_filter_label, '', 'src', admin_static_prefix + 'img/selector-search.gif', 'class', 'help-tooltip', 'alt', '', 'title', interpolate(gettext("Type into this box to filter down the list of available %s."), [field_name]));
filter_p.appendChild(document.createTextNode(' '));
var filter_input = quickElement('input', filter_p, '', 'type', 'text', 'placeholder', gettext("Filter"));
filter_input.id = field_id + '_input';
selector_available.appendChild(from_box);
var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'title', interpolate(gettext('Click to choose all %s at once.'), [field_name]), 'href', 'javascript: (function(){ SelectBox.move_all("' + field_id + '_from", "' + field_id + '_to"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_add_all_link');
choose_all.className = 'selector-chooseall';
// <ul class="selector-chooser">
var selector_chooser = quickElement('ul', selector_div, '');
selector_chooser.className = 'selector-chooser';
var add_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Choose'), 'title', gettext('Choose'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_from","' + field_id + '_to"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_add_link');
add_link.className = 'selector-add';
var remove_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Remove'), 'title', gettext('Remove'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_to","' + field_id + '_from"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_remove_link');
remove_link.className = 'selector-remove';
// <div class="selector-chosen">
var selector_chosen = quickElement('div', selector_div, '');
selector_chosen.className = 'selector-chosen';
var title_chosen = quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s') + ' ', [field_name]));
quickElement('img', title_chosen, '', 'src', admin_static_prefix + 'img/icon-unknown.gif', 'width', '10', 'height', '10', 'class', 'help help-tooltip', 'title', interpolate(gettext('This is the list of chosen %s. You may remove some by selecting them in the box below and then clicking the "Remove" arrow between the two boxes.'), [field_name]));
var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name'));
to_box.className = 'filtered';
var clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', interpolate(gettext('Click to remove all chosen %s at once.'), [field_name]), 'href', 'javascript: (function() { SelectBox.move_all("' + field_id + '_to", "' + field_id + '_from"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_remove_all_link');
clear_all.className = 'selector-clearall';
from_box.setAttribute('name', from_box.getAttribute('name') + '_old');
// Set up the JavaScript event handlers for the select box filter interface
addEvent(filter_input, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); });
addEvent(filter_input, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); });
addEvent(from_box, 'change', function(e) { SelectFilter.refresh_icons(field_id) });
addEvent(to_box, 'change', function(e) { SelectFilter.refresh_icons(field_id) });
addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); SelectFilter.refresh_icons(field_id); });
addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); SelectFilter.refresh_icons(field_id); });
addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); });
SelectBox.init(field_id + '_from');
SelectBox.init(field_id + '_to');
// Move selected from_box options to to_box
SelectBox.move(field_id + '_from', field_id + '_to');
if (!is_stacked) {
// In horizontal mode, give the same height to the two boxes.
var j_from_box = $(from_box);
var j_to_box = $(to_box);
var resize_filters = function() { j_to_box.height($(filter_p).outerHeight() + j_from_box.outerHeight()); }
if (j_from_box.outerHeight() > 0) {
resize_filters(); // This fieldset is already open. Resize now.
} else {
// This fieldset is probably collapsed. Wait for its 'show' event.
j_to_box.closest('fieldset').one('show.fieldset', resize_filters);
}
}
// Initial icon refresh
SelectFilter.refresh_icons(field_id);
},
refresh_icons: function(field_id) {
var from = $('#' + field_id + '_from');
var to = $('#' + field_id + '_to');
var is_from_selected = from.find('option:selected').length > 0;
var is_to_selected = to.find('option:selected').length > 0;
// Active if at least one item is selected
$('#' + field_id + '_add_link').toggleClass('active', is_from_selected);
$('#' + field_id + '_remove_link').toggleClass('active', is_to_selected);
// Active if the corresponding box isn't empty
$('#' + field_id + '_add_all_link').toggleClass('active', from.find('option').length > 0);
$('#' + field_id + '_remove_all_link').toggleClass('active', to.find('option').length > 0);
},
filter_key_up: function(event, field_id) {
var from = document.getElementById(field_id + '_from');
// don't submit form if user pressed Enter
if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) {
from.selectedIndex = 0;
SelectBox.move(field_id + '_from', field_id + '_to');
from.selectedIndex = 0;
return false;
}
var temp = from.selectedIndex;
SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value);
from.selectedIndex = temp;
return true;
},
filter_key_down: function(event, field_id) {
var from = document.getElementById(field_id + '_from');
// right arrow -- move across
if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) {
var old_index = from.selectedIndex;
SelectBox.move(field_id + '_from', field_id + '_to');
from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index;
return false;
}
// down arrow -- wrap around
if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) {
from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
}
// up arrow -- wrap around
if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) {
from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1;
}
return true;
}
}
})(django.jQuery);
##INSTALLED APPS##
########## APP CONFIGURATION
INSTALLED_APPS = DJANGO_APPS + LOCAL_APPS
DJANGO_APPS = (
'django_gulp',
# Default Django apps:
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
# Useful template tags:
# 'django.contrib.humanize',
# Python-Social-Auth
'social_django',
# Admin panel and documentation:
'django.contrib.admin',
'django.core.management',
# 'django.contrib.admindocs',
# for task queue
# For django-storages static store on AWS S3
'storages',
# Other django apps
'rest_framework',
)
# Apps specific for this project go here.
LOCAL_APPS = (
'base',
'myproject',
'users',
'campaigns',
)
I copied the static/admin files from Django.contrib using the following command in terminal:
cp -a /Users/username/.virtualenvs/rs/lib/python3.9/site-packages/django/contrib/admin/. /Users/username/Documents/myproject/static/
I then ran collectstatic to upload the files to S3, where they are stored for production. This actually worked, but seems a bit hacky. I must not be doing something right for these files to not update upon Django upgrade.

Multiple Search Fields not working as expected

I have 3 search field(event_name, event_loc, event_date), while one of them filled it return all table data, while it has to be return one.
views.py
def searchEvent(request):
if request.is_ajax():
q_event_name = request.POST.get('event_name')
q_event_loc = request.POST.get('event_loc')
q_event_date = request.POST.get('event_date')
# return JsonResponse(dict(events=list(Event.objects.values('name', 'info'))))
return JsonResponse(
serializers.serialize('json', Event.objects.filter(
Q(name__icontains=q_event_name) | Q(info__icontains=q_event_loc) | Q(start_event_dt__icontains=q_event_date))),
safe=False)
custom.js
$.ajax({
url: '/searchevent/',
data: {
'event_name': event_name,
'event_loc': event_loc,
'event_date': event_date
},
dataType: 'json',
type: 'POST',
success: function (data) {
data = JSON.parse(data);
event_html = '';
for(var i in data){
event_name = data[i]['fields']['name'];
event_html = event_html + '<div class="col-md-4">' +
' <h5 class="card-title text-center pt-3">' + event_name + '</h5>' +
'</div>';
}
document.getElementById("event-container").innerHTML = event_html;
}
});
for example, I have two event in my DB
name=aaa, loc=bbb, date=2019-01-01
name=ccc, loc=ddd, date=2018-01-01
in the search bar when I searched for only name field with aaa, it returns all of the two event, which i expected to return me only first event.
You are using Q-expressions to build up your query (or-conditions).
So your filter expression will translate to:
name__icontains=q_event_name OR info__icontains=q_event_loc OR start_event_dt__icontains=q_event_date
If q_event_loc is for example an empty string - icontains will match every info column which is not null.

in tastypie, how can i set name for json result

In tastypie, I want set json result name.
I have a class that I use for it but I can set name in.
enter cclass ContentResource(ModelResource):
class Meta:
results = ListField(attribute='results')
queryset = Content.objects.all()
resource_name = 'content'
max_limit = None
#filtering = {"title": "contains"}
def alter_list_data_to_serialize(self, request, data_dict):
if isinstance(data_dict, dict):
if 'meta' in data_dict:
# Get rid of the "meta".
del(data_dict['meta'])
# Rename the objects.
data_dict['Mobile'] = data_dict['objects']
del(data_dict['objects'])
return data_dict
ode here it returns this
{"Mobile":
[
{
"added": "2015-07-23T11:30:20.911835",
"content_cast": "",
"content_company": "HamrahCinema",
"content_description": "so nice",
"content_director": "",
"content_duration": "2:20",
"content_filelanguage": null,
}
]
}
when I use /content/api/content every thing is ok, but when I use /content/api/content/1,"mobile" is removed.
as educated guess, I would suggest using alter_detail_data_to_serialize