Django - Autocomplete Light in admin for join Model - django-admin

So I have a join model defined as follow:
class EventTrack(models.Model):
dj = models.ForeignKey(DjProfile, blank=True)
track = models.ForeignKey(Track, blank=True)
event = models.ForeignKey(Event, blank=True)
def __str__(self):
return '%s - %s' % (self.event, self.track)
Is there a way I can use django-autocomplete-light with this model?
I know how to use it with inline models, but I don't get how to use it with standard field (in this case they are fk though).
At the moment I have the follow, which does not include the autocomplete functionality:
class EventTrackAdmin(admin.ModelAdmin):
fields = ['event', 'dj', 'track']
list_display = ('event', 'dj', 'track')
search_fields = ['event', 'dj', 'track']
admin.site.register(EventTrack, EventTrackAdmin)
edit:
I defined a DjForm as follow:
class DjForm(ModelForm):
dj = ModelChoiceField(
queryset=DjProfile.objects.all(),
widget=autocomplete.ModelSelect2(url='dj-autocomplete')
)
class Meta:
model = DjProfile
fields = '__all__'
Views:
#method_decorator(login_required, name='dispatch')
class DjProfileAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
if not self.request.user.is_authenticated():
return DjProfile.objects.none()
qs = DjProfile.objects.all()
if self.q:
qs = qs.filter(name__istartswith=self.q)
return qs
And routing:
from frontend.views import DjProfileAutocomplete
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^dj-autocomplete/$', DjProfileAutocomplete.as_view(create_field='name'), name='dj-autocomplete'),
]
Everything works fine, and if I browse the endpoint I get the json with Djs result. My only problem is to use this in the EventTrack Model, in the admin.

sooo, actually it was easy:
forms.py
class EventTrackForm(ModelForm):
dj = ModelChoiceField(
queryset=DjProfile.objects.all(),
widget=autocomplete.ModelSelect2(url='dj-autocomplete')
)
class Meta:
model = EventTrack
fields = '__all__'
admin.py
class EventTrackAdmin(admin.ModelAdmin):
form = EventTrackForm
...

Related

how to display the fields of related models in the admin panel through relationships model

I'm having trouble with the admin panel. The problem arose due to the presence of relationships model 'ElementVersion'. I don’t know how to display the fields of related models in the admin panel through ElementVersion.
My models.py file
class Refbook(models.Model):
code = models.CharField(max_length=100, unique=True, verbose_name='refbook code')
name = models.CharField(max_length=300, verbose_name='refbook name')
description = models.TextField()
def __str__(self):
return self.name
class Version(models.Model):
refbook_id = models.ForeignKey('Refbook',
on_delete=models.CASCADE,
related_name='versions',
verbose_name='refbook name')
version = models.CharField(max_length=50, verbose_name='refbook version')
date = models.DateField(verbose_name='start date')
def __str__(self):
return self.version
class Meta:
unique_together = [['refbook_id', 'version'], ['refbook_id', 'date']]
class Element(models.Model):
version_id = models.ManyToManyField('Version',
through='ElementVersion',
verbose_name='id_version')
code = models.CharField(max_length=100, unique=True, verbose_name='element's code')
value = models.CharField(max_length=300, verbose_name='element's value')
def __str__(self):
return self.code
class ElementVersion(models.Model):
version = models.ForeignKey(Version,
on_delete=models.CASCADE,
verbose_name='version')
element = models.ForeignKey(Element,
on_delete=models.CASCADE,
verbose_name='element')
class Meta:
unique_together = ('version', 'element')
This is the admin.py file
from .models import Refbook, Version, Element, ElementVersion
class VersionInline(admin.StackedInline):
model = Version
fields = ['date']
readonly_fields = ('date',)
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
#admin.register(Refbook)
class RefbookAdmin(admin.ModelAdmin):
list_display = ['id', 'code', 'name', 'get_last_version', 'get_date_version']
inlines = [VersionInline]
def get_last_version(self, obj):
return obj.versions.values_list('version').last()
get_last_version.short_description = "last version"
def get_date_version(self, obj):
return obj.versions.values_list('date').last()
get_date_version.short_description = "start date"
class ElementVersionInline(admin.TabularInline):
model = ElementVersion
extra = 0
#admin.register(Version)
class VersionAdmin(admin.ModelAdmin):
list_display = ['get_code_refbook', 'refbook_id', 'version', 'date']
inlines = [ElementVersionInline]
def get_code_refbook(self, obj):
return obj.refbook_id.code
get_code_refbook.short_description = 'refbook code'
#admin.register(Element)
class ElementAdmin(admin.ModelAdmin):
list_display = ['id', 'code', 'value']
inlines = [ElementVersionInline]
I want to solve two problems:
on the version editing page, you need the ability to fill in the elements in this version. At the same time, I want all fields with readable names to be displayed, that is, the code and value
on the element editing page, you need the ability to select "reference-version" from the double list

Groovy dynamically invoked class and find method doesn't work?

I trying to build a dynamic query similar to:
def domain = DomainName
def ids = 1
def domainClass = "$domain" as Class
domainClass.find("from ${domain} as m where m.job = ${ids} ").id
But it's not working.
If I'm trying this, all is fine:
def domain = DomainName
def ids = 1
DomainName.find("from ${domain} as m where m.job = ${ids} ").id
How can I use dynamic domain class name with find?
The simplest way is to use the getDomainClass method:
String domainClassName = 'com.foo.bar.Person'
def ids = 1
def domainClass = grailsApplication.getDomainClass(domainClassName).clazz
domainClass.find("from $domainClassName as m where m.job = ${ids} ").id
Note that if you're trying to get a single instance by id, use get:
long id = 1234
def person = domainClass.get(id)
and if you want to get multiple instances and you have a list of ids, you can use getAll
def ids = [1,2,3,4,5]
def people = domainClass.getAll(ids)
Also it's a REALLY bad idea to use GStrings with property values embedded - Google 'SQL Injection'
For example to find a person by username:
String username = 'foo'
def person = domainClass.find(
"from $domainClassName as m where m.username=:username",
[username: username])
You should be able to do this by explicitly using the GroovyClassLoader:
def domain = "DomainName"
def c = new GroovyClassLoader().loadClass(domain)
c.find('...').id
The best way to get a Domain class dynamically is through the GrailsApplication object. Example:
import org.codehaus.groovy.grails.commons.ApplicationHolder
def domainName = "full.package.DomainName"
def domainGrailsClass = ApplicationHolder.application.getArtefact("Domain", domainName)
def domainClass = domainGrailsClass.getClazz()
domainClass.find("from ${domainGrailsClass.name} as m where m.job = ${ids}").id
You can also use Class.forName() just as you would in Java. Use the 3 parameter version and pass in the current thread context class loader:
import grails.util.GrailsNameUtils
def domainName = "full.package.DomainName"
def domainClass = Class.forName(domainName, true, Thread.currentThread().getContextClassLoader())
domainClass.find("from ${GrailsNameUtils.getShortName(domainName)} as m where m.job = ${ids}").id
Classloaders are an ugly topic in Java and JVM frameworks. In Grails, you almost always want to use the thread context classloader. But even better is to use the GrailsApplication interface and avoid the issue altogether.
use GrailsClassUtils
GrailsClassUtils.getShortName(DomainName)
to get the name of the class, so this should work... if I understood the question
def domainClassName = GrailsClassUtils.getShortName(DomainName)
def ids = 1
DomainName.find("from ${domainClassName} as m where m.job = ${ids} ").id

Django Admin: Many-to-Many listbox doesn't show up with a through parameter

I have the following models:
class Message(models.Model):
date = models.DateTimeField()
user = models.ForeignKey(User)
thread = models.ForeignKey('self', blank=True, null=True)
...
class Forum(models.Model):
name = models.CharField(max_length=24)
messages = models.ManyToManyField(Message, through="Message_forum", blank=True, null=True)
...
class Message_forum(models.Model):
message = models.ForeignKey(Message)
forum = models.ForeignKey(Forum)
status = models.IntegerField()
position = models.IntegerField(blank=True, null=True)
tags = models.ManyToManyField(Tag, blank=True, null=True)
In the admin site, when I go to add/change a forum, I don't see the messages listbox as you'd expect. However, it shows up if I remove the 'through' parameter in the ManyToManyField declaration. What's up with that? I've registered all three models (plus Tag) to the admin site in admin.py.
TIA
Documentation says:
When you specify an intermediary model using the through argument to a
ManyToManyField, the admin will not display a widget by default.
But it's probably possible to display M2M fields in the admin change view even if the through attribute is defined.
class ForumAdminForm(forms.ModelForm):
mm = forms.ModelMultipleChoiceField(
queryset=models.Message.objects.all(),
widget=FilteredSelectMultiple(_('ss'), False, attrs={'rows':'10'}))
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
initial = kwargs.setdefault('initial', {})
initial['mm'] = [t.service.pk for t in kwargs['instance'].message_forum_set.all()]
forms.ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
instance = forms.ModelForm.save(self, commit)
old_save_m2m = self.save_m2m
def save_m2m():
old_save_m2m()
messages = [s for s in self.cleaned_data['ss']]
for mf in instance.message_forum_set.all():
if mf.service not in messages:
mf.delete()
else:
messages.remove(mf.service)
for message in messages:
Message_forum.objects.create(message=message, forum=instance)
self.save_m2m = save_m2m
return instance
class Meta:
model = models.Forum
class ForumAdmin(admin.ModelAdmin):
form = ForumAdminForm
Take a look at the official documentation:
I learned a lot from #Fedor's answer, but some comments and cleanup may be still beneficial.
class ForumAdminForm(forms.ModelForm):
messages = forms.ModelMultipleChoiceField(
queryset=Message.objects.all(),
widget=FilteredSelectMultiple('Message', False))
# Technically, you don't need to manually set initial here for ForumAdminForm
# However, you NEED to do the following for MessageAdminForm
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
# a record is being changed. building initial
initial = kwargs.setdefault('initial', {})
initial['messages'] = [t.message.pk for t in kwargs['instance'].message_forum_set.all()]
super(ForumAdminForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
if not self.is_valid():
raise HttpResponseForbidden
instance = super(ForumAdminForm, self).save(self, commit)
def save_m2m_with_through():
messages = [t for t in self.cleaned_data['messages']
old_memberships = instance.message_forum_set.all()
for old in old_memberships:
if old.message not in messages:
# and old membership is cleaned by the user
old.delete()
for message in [x for x in messages not in map(lambda x: x.message, old_memberships)]:
membership = Member_forum(message=messsage, forum=instance)
# You may have to initialize status, position and tag for your need
membership.save()
if commit:
save_m2m_with_through()
else:
self.save_m2m = save_m2m_with_through
return instance
class Meta:
model = Forum
fields = {'name', 'messages')
There's one caveat: if you have another many-to-many relationship in the models (that is without through), super(ForumAdminForm, self).save(self, commit) will set self.save_m2m in case commit is False. However, calling this would cause an error, because this function also tries to save the many-to-many with through as well. You may need to save all other many-to-many relationship manually, or catch the exception, or else.
Django admin nicely support many-to-many intermediary models that using the through argument .
For example you have these Person and Group models with intermediate Membership model:
models.py
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
Now in admin.py file ,
Define an inline class for the intermediate Membership model:
#admin.register(Membership)
class MembershipInline(admin.TabularInline):
model = Membership
extra = 1
And use them in admin views of models:
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
inlines = (MembershipInline,)
#admin.register(Group)
class GroupAdmin(admin.ModelAdmin):
inlines = (MembershipInline,)
More info in official docs:
Models,
Admin

django admin - how to override the default <select> view for foreign key?

I have 2 django model
class Location
address = models.CharField(max_length=100)
city = models.CharField(max_length=20)
class Client
location = models.ForeignKey(Location)
name = models.CharField(max_length=100)
And I have the following admin.py
class ClientAdmin(admin.ModelAdmin):
fieldsets = [
('Client Details', {'fields': ['name']}),
('Location Details', {'fields': ['location']}),
]
admin.site.register(Client, ClientAdmin)
When I try to add a Client I like to have a editable "location" fields i.e. address and city text fields. django by default gives a list.. how can I get the fields from the foregin tables?
any way I can do this? I don't wish to register the Location table in the admin.
thanks
You could put an inline table for the location and use the method queryset to handle the field values.
So, this is the ideia of the solution:
class LocationInline(admin.StackedInline):
model = Location
def queryset(self, request):
query = ... #your custom query here
qs = super(LocationInline, self).queryset(request).filter(query)
return qs
class ClientAdmin(admin.ModelAdmin):
inlines = [LocationInline]
fieldsets = [
('Client Details', {'fields': ['name']}),
]
Edit:
There is another way to do it in django 1.1.
class ClientAdmin(admin.ModelAdmin):
fieldsets = [
('Client Details', {'fields': ['name']}),
('Location Details', {'fields': ['location']}),
]
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name = "location":
locations = Location.objects.filter( ... ) #your query
kwargs["queryset"] = Location.objects.filter(query)
return super(ClientAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
The docs are here: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
It's an old question, but answering this can help a lot of other people too.
So, I hope it helps! ;)

Order Django admin change list column by output of __unicode()__ method

Here's part of my Django app's models.py:
class Person(models.Model):
birth_year = WideYear(null=True, blank=True)
birth_year_uncertain = models.BooleanField()
death_year = WideYear(null=True, blank=True)
death_year_uncertain = models.BooleanField()
flourit_year = WideYear(null=True, blank=True)
flourit_year_uncertain = models.BooleanField()
FLOURIT_CHOICES = (
(u'D', u'Birth and death dates'),
(u'F', u'Flourit date'),
)
use_flourit = models.CharField('Date(s) to use', max_length=2, choices=FLOURIT_CHOICES)
index_entries = models.ManyToManyField(IndexEntry, null=True, blank=True)
def __unicode__(self):
if self.personname_set.filter(default_name__exact=True):
name = z(self.personname_set.filter(default_name__exact=True)[0])
else:
name = u'[Unnamed person]'
if self.use_flourit == u'D':
dates = '%s - %s' % (z(self.birth_year), z(self.death_year))
else:
dates = 'fl. ' + z(self.flourit_year)
return '%s (%s)' % (name, dates)
class PersonName(models.Model):
titles = models.CharField(max_length=65535, null=True, blank=True)
surname = models.CharField(max_length=255, null=True, blank=True)
first_name = models.CharField(max_length=255, null=True, blank=True)
middle_names = models.CharField(max_length=255, null=True, blank=True)
post_nominals = models.CharField(max_length=65535, null=True, blank=True)
default_name = models.BooleanField()
person = models.ForeignKey(Person, null=True, blank=True)
def __unicode__(self):
return '%s, %s %s' % (self.surname, self.first_name, self.middle_names)
class Meta:
unique_together = ("titles", "surname", "first_name", "middle_names", "post_nominals", "person")
unique_together = ("default_name", "person")
and here are the corresponding parts of my app's admin.py:
from reversion.admin import VersionAdmin
class PersonNameInline(admin.TabularInline):
model = PersonName
extra = 1
class PersonAdmin(VersionAdmin):
radio_fields = {"use_flourit": admin.HORIZONTAL}
inlines = [PersonNameInline]
admin.site.register(Person, PersonAdmin)
In the admin, this produces a change list as follows:
(source: sampablokuper.com)
As you can see, although the change list populates each row of the Person column with the output of the __unicode()__ method of the Person class, it does not order the rows of that column by the __unicode()__ method of the Person class.
How can I make it do so?
Many thanks in advance!
Django ordering is done in the database level. Unless you store the result of your unicode function in the DB, django is not going to be able to natively return results ordered in that fashion.
Storing an ordering value in the DB is probably the most expedient way to solve this problem.
Just hit the same problem, and I found the following link useful. Instead of sorting by unicode, it tries to sort by multiple columns, which may help solve the problem:
http://djangosnippets.org/snippets/2110/
Following Paul McMillan's suggestion, I added the following line to the class definition of Person:
ordering_string = models.CharField(max_length=255, null=True, blank=True)
I also put the this above the PersonName definition in models.py:
def post_save_person_and_person_name(sender, **kwargs):
person_name = kwargs['instance']
person = person_name.person
if person.ordering_string != unicode(person)[0:254]:
person.ordering_string = unicode(person)[0:254]
super(Person, person).save()
and put this below the PersonName definition in models.py:
post_save.connect(post_save_person_and_person_name, sender=PersonName)
So far, so good.
I think I might be able to improve on it by replacing the save() call above with a queryset update() . I'd welcome suggestions on that front!

Resources