customize django admin field based on another drop-down field - django

I have a simple Django app with three models:
class Category(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(("Description"))
class ItemType(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(("Description"))
category = models.ForeignKey(Category, on_delete=models.CASCADE)
class Plant(models.Model):
category = models.ForeignKey(Category , on_delete=models.SET_NULL , null=True)
type = models.ForeignKey(ItemType, on_delete=models.SET_NULL,null = True)
name = models.CharField()
what I need is in the admin panel when I chose a category the type dropdown is filter base on that for example I have category1 and category2 and type1 related to category1 and type2 related to category2 when I choose category1 from the dropdown the type dropdown show only type1

I had to do this the other day, it's a bit of a pain, but doable and I've used it several times since I've come up with this method.
Steps
Add the required information to the context in render_change_form.
For your instance I'd imagine you'd want something along the lines of this
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
# Implement get_map to return a hierarchical dict that represents your category, item type, and plant
context['map'] = self.get_map()
return super().render_change_form(request, context, add, change, form_url, obj)
Hide rows that can't be filed yet.
window.addEventListener('DOMContentLoaded', (event) => {
django.jQuery("div.field-item_type").css('display', '');
django.jQuery("div.field-plant").css('display', '');
});
Add event listeners to update dependent fields
# When category is changed, you'll need to update options for item_type and plant
django.jQuery("select[name=category]").change(function () {
let category = django.jQuery(this)
update_item_type(category)
update_plant(django.jQuery('select[name=category]'))
});
# When item_type is changed, you'll only need to plant options
django.jQuery("select[name=item_type]").change(function () {
let item_type = django.jQuery(this)
update_plant(item_type)
});
implement update_item_type and update_plant to update options on the respective fields
They'll look something like this
function update_item_type(plant) {
let starting_item_type = django.jQuery("select[name=item_type]").val()
if (plant.val() === '') {
django.jQuery("div.field-item_type").css('display', 'none');
} else {
let item_type_dict = {{ item_type_dict|safe }};
let item_type_dom = django.jQuery('#id_item_type');
item_type_dom.text('')
django.jQuery.each(item_type_dict[plant.val()], function(index, element) {
item_type_dom.append('<option value="' + element[0] + '">' + element[1] + '</option>');
});
django.jQuery("div.field-item_type").css('display', '');
django.jQuery("select[name=item_type]").val(starting_item_type)
}
}
Access your dictionary from Django land like this
let map = {{ map|safe }};
Follow-Up
I probably should create a Django package to do this automatically. If anyone knows of one already I'd be interested to hear about it.

Related

django rest datatables, filter experts by meeting objectives

I need to filter all Experts by past objectives.
I have a minimal runnable example at https://github.com/morenoh149/django-rest-datatables-relations-example (btw there are fixtures you can load with test data).
my models are
class Expert(models.Model):
name = models.CharField(blank=True, max_length=300)
class Meeting(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True)
expert = models.ForeignKey(Expert, on_delete=models.SET_NULL, blank=True, null=True)
objective = models.TextField(null=True, blank=True)
my datatables javascript
$("#table-analyst-search").DataTable({
serverSide: true,
ajax: "/api/experts/?format=datatables",
ordering: false,
pagingType: "full_numbers",
responsive: true,
columns: [
{
data: "objectives",
name: "objectives",
visible: false,
searchable: true,
render: (objectives, type, row, meta) => {
return objectives;
}
},
],
});
My serializer
class ExpertSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
objectives = serializers.SerializerMethodField()
class Meta:
model = Expert
fields = (
"id",
"objectives",
)
def get_objectives(self, obj):
request = self.context["request"]
request = self.context["request"]
meetings = Meeting.objects.filter(
analyst_id=request.user.id, expert_id=obj.id
).distinct('objective')
if len(meetings) > 0:
objectives = meetings.values_list("objective", flat=True)
objectives = [x for x in objectives if x]
else:
objectives = []
return objectives
When I begin to type in the datatables.js searchbar I get an error like
FieldError at /api/experts/
Cannot resolve keyword 'objectives' into field. Choices are: bio, company, company_id, created_at, description, email, favoriteexpert, first_name, id, is_blocked, last_name, meeting, middle_name, network, network_id, position, updated_at
Request Method: GET
Request URL: http://localhost:8000/api/experts/?format=datatables&draw=3&columns%5B0%5D%5Bdata%5D=tags&columns%5B0%5D%5Bname%5D=favoriteexpert.tags.name&columns%5B0%5D%5Bsearchable%5D=true&columns%5B0%5D%5Borderable%5D=false&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=desc&columns%5B1%5D%5Bname%5D=&columns%5B1%5D%5Bsearchable%5D=false&columns%5B1%5D%5Borderable%5D=false&columns%5B1%5D%5Bsearch%5D%5Bvalue%5D=&columns%
fwiw, in pure django orm what I want to accomplish would be something like
Expert.objects.filter(
pk__in=Meeting.objects.filter(
objective__icontains='Plastics', user=request.user
).values('expert')
)
How can I filter experts by historical meeting objectives?
The reason for the error is that django-rest-framework-datatables is trying to translate the request into a query which can be run against the Expert table.
In your JS, you're asking for a field called 'objectives' to be returned, but there is no such field on the Expert model.
You could probably achieve what you are trying to do using the django-filter integration. In this case, you could set up a filter on the FK reference to the Meeting table. The example app demonstrates how to do this.
I think the best way to understand what's going on is to get the example application running, and if possible, set breakpoints and step through.
Incidentally, if you want to get the search box to work correctly, then you need to define a global_q() method. This is also covered in the example app.
I ended up authoring a custom django-filter
class AssociatedMeetingCharFilter(filters.CharFilter):
def global_q(self):
"""
Uses the global filter to search a meeting field of meetings owned by the logged in user
"""
if not self._global_search_value:
return Q()
kw = "meeting__{}__{}".format(self.field_name, self.lookup_expr)
return Q(**{
kw: self._global_search_value,
"meeting__user_id": self.parent.request.user.id or -1,
})
class ExpertGlobalFilterSet(DatatablesFilterSet):
name = GlobalCharFilter(lookup_expr='icontains')
objectives = AssociatedMeetingCharFilter(field_name='objective', lookup_expr='icontains')
full example at https://github.com/morenoh149/django-rest-datatables-relations-example

Django query- 'not equal to' filter

I am writing a Django query to filter a list of teams which the user is a part of.
(The Team and UserTeams models I am querying):
class Team(models.Model)
name = models.CharField(max_length=100)
venue = models.CharField(max_length=100)
countryID = models.ForeignKey(Countries, on_delete=models.CASCADE)
owner = models.ForeignKey(User)
class UserTeams(models.Model):
userID = models.ForeignKey(User,on_delete=models.CASCADE)
teamID = models.ForeignKey(Team,on_delete=models.CASCADE)
I'm struggling to make two queries:
Query 1:
The first query (teamquery) filters the Teams by checking if the owner=request.user, and I then display a list of these teams in my template.
Query 2: I then want to display a list of teams where the UserTeams UserID = request.user (userteamquery)
Problem:
Some teams are appearing in both query results. Is there a 'not equal' query I can use where it will exclude all UserTeams in the userteamquery where the teamID is a result of teamquery? so teamID=teamquery
#login_required
def teamsview(request):
teamquery = Team.objects.filter(owner=request.user)
userteamquery = UserTeams.objects.filter(userID=request.user)
return render(request, 'teammanager/teams.html', {
"teams": teamquery, "userteams": userteamquery})
Tried to exclude() (pretty sure this isn't even valid use)
userteamquery =
UserTeams.objects.exclude(teamID=teamquery).filter(userID=request.user)
This didnt change anything at all in my output
Also tried ~Q but didnt change anything either:
userteamquery = UserTeams.objects.filter(Q(userID=request.user),
~Q(teamID=teamquery))
*Edit -
Added __in to my parameter, this works:
userteamquery = UserTeams.objects.filter(Q(userID=request.user),
~Q(teamID__in=teamquery))

Odoo - How to update a pre defined DATE field value

I'm trying to modify hr.contract model so the 'end_date' field gets the value of 'effective_date' which is in another model 'resignation_application'.
The concept is when an employee fill a resignation application it updates the contract end date.
Here's my code:
class resignation_application(osv.osv):
_name = 'resignation.application'
_columns = {
'employee_id': fields.many2one('hr.employee', "Employee", select=True, invisible=False, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
'effective_date': fields.date('Effective Date', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}, select=True, copy=False),
class hr_contract(osv.osv):
_inherit = 'hr.contract'
_columns = {
'end_date': fields.date('End Date', compute= '_compute_effective_date', store=True),
}
#api.model
def create(self, values):
if 'end_date' in values and not values['end_date']:
del(values['end_date'])
return super(hr_contract, self).create(values)
#api.one
#api.depends('end_date','employee_id')
def _compute_effective_date(self):
recs = self.env['resignation.application'] # retrieve an instance of MODEL
recs = recs.search([('state', '=', 'validate')]) # search returns a recordset
for rec in recs: # iterate over the records
if self.employee_id == rec.employee_id:
self.end_date = rec.effective_date
return recs.write({'end_date': rec.effective_date})
But it didn't return the end date.. I know there's something wrong with my return statement but I don't know how to fix it.. Also I want to add an inverse method to end_date field so the hr officer can add an end date to the employee contract. Any help will be appreciated.
First, you are mixing v8 (decorators) and v7 (osv.osv and _columns) stuffs.
Besides, in v8 you don't need to return anything nor write directly the end_date field in the compute method, but just set the field as you already did. Did you try to just remove the return statement?

Create a dynamic search form in Django

in my Django 1.5 model I'm trying to create a form that let me book a place in a study room.
The study room is defined as follows in models.py:
class StudyRoom(models.Model):
name = models.CharField(max_length = 30, primary_key = True)
city = models.CharField(max_length = 30)
placesno = models.IntegerField()
def __unicode__(self):
return self.name
and this is the relative form:
class SearchSeat(forms.Form):
morning = 'AM'
evening = 'PM'
daysection_choices = ((morning, 'Morning'), (evening, 'Evening'),)
city = forms.ChoiceField(choices = [], required=True, label='Select a city?')
study_room = forms.ChoiceField(choices = [], required=True, label='Select a study room?')
day = forms.DateField(label = 'Select a day', required=True, widget=forms.extras.SelectDateWidget(years=range(2014, 2015)))
section = forms.ChoiceField(choices=daysection_choices, label = 'Morning (form 8.00 to 13.00) or evening (from 13.00 to 18..)?')
def __init__(self, *args, **kwargs):
super(SearchSeat, self).__init__(*args, **kwargs)
self.fields['city'].choices = StudyRoom.objects.all().values_list("city","city").distinct()
search_city = self.fields['city']
self.fields['study_room'].choices = StudyRoom.objects.filter(city = search_city).values_list("name")
The objective was to let the user select the city and then filter the study room and show only the ones in the selected city, all in one form without changing page.
The code written like this doesn't work and I'm starting to think that there isn't a solution without using client side side scripting (it's a Django+Python project so we are supposed to use only those two instruments).
For your problem there can only be a solution with client scripting:
At the moment the html is created, the study-room choices are not determined. This means that on city change, you will need to manipulate your html, which means client side programming like:
$(document).ready(function(){
$('#id_city').on('change', function(){
...
})
);
There is no need for an ajax request, though: you could save the choices into a 'data' attribute in your html and access it using: http://api.jquery.com/data/
You would need then to modify your fields:
self.fields['city'] = forms.Select(attrs={'data-london':'[json_dump of londondata], 'data-paris': '[json_dump of paris study rooms]' etc})
Depending on the amount of data, the ajax call would be a cleaner solution

Django Select Widget Groups template

I have the following models:
# Group for Key/Value pairs
class Group(models.Model):
name = models.TextField(unique=True)
def __unicode__(self):
return self.name
class Meta:
verbose_name = 'Group'
verbose_name_plural = 'Groups'
# Key is the key/name for a Value
class Key(models.Model):
name = models.TextField(unique=True)
def __unicode__(self):
return self.name
class Meta:
verbose_name = 'Key'
verbose_name_plural = 'Keys'
# Value is the value/content of a key
class Value(models.Model):
value = models.TextField()
def __unicode__(self):
return '%s' % self.value
class Meta:
verbose_name = 'Value'
verbose_name_plural = 'Values'
class Key_Value(models.Model):
group = models.ForeignKey(Group)
key = models.ForeignKey(Key)
value = models.ForeignKey(Value)
def __unicode__(self):
return '%s = %s' % (self.key.name, self.value.value)
class Meta:
verbose_name = 'Key/Value Paar'
verbose_name_plural = 'Key/Value Paare'
Now I pass the form to the template:
def exampleview(request):
key_value_form = Key_Value_Form(request.POST)
return render_to_response(
'edit.html', {
'key_value_form': key_value_form,
})
Now lets look at possible data
KEY/VALUE PARIRS:
key = TEST 1
value = TEST 1
group = TESTGROUP 1
key = TEST 2
value = TEST 2
group = TESTGROUP 2
Now I changed the default widgets for the Key/Value Table entries to select widgets.
Here's what I want to do:
SELECT GROUP [+] [-]
--> [Now choose Key/Value pair belonging to group] [+] [-]
at the start you always get two selects one for the group and one for the key/value pair.
if you press the + at the GROUP a new group select button should appear along with a KEY/Value Pair select, if you press the + at the Key/Value select a new key/value select box should appear.
I have two problems:
ONE: I don't know how the check in the template should look like and
TWO: How I can implement those + - Buttons
Any Help is appreciated. It would be cool if this would be possible without javascript but I don't have very high hopes in that direction
You need to build the form on the fly. Suppose you had a definition of a group form built in a dictionary from a view in your view handler (containing group model foreign keys and number of actual keys in them):
# note that the dictionary d shown here is just an example
# yours should have actual group foreign keys based on Group model
d = { 'first' : 5, 'second' : 3, 'third' : 2 }
def make_groupkeys_form(descriptor):
groups = { 'select': '', 'keypairs' : [] }
fields = { 'groups' : groups }
for group, numkeys in descriptor.values():
groupchoices = # find out what choices you need to offer for group widget
groups['select'] = forms.SelectField(choices=groupchoices)
for i in range(numkeys):
keyvalchoices = # find out what choices you need to offer for keyval widget per group
groups['keypairs'].append(forms.SelectField(choices=keyvalchoices))
# now you have enough SelectFields for all your groups and keyval pairs
return type('Key_Value_Form', (forms.BaseForm,), { 'base_fields': fields })
# from view handler
def exampleview(request):
# determine from request how many groups you want and how many keyval widgets per group, for example here I will use the above predefined d
key_value_form = make_groupkeys_form(d)
return render_to_response(
'edit.html', {
'key_value_form': key_value_form,
})
Note that you can (and should) rewrite a class Key_Value_Form to inherit from forms.BaseForm and put the make_groupkeys_form(descriptor) code in its __init__(request, descriptor) member. You will also need to do write your own is_valid() to enumerate through select fields and make sure the choices are correct when the user submits the form and override clean() to attempt to validate individual user's choices.
Finally, consider going through this dynamic forms read. It will guide you step by step in how to create dynamic forms in django.