GeoDjango: Can I use OSMGeoAdmin in an Inline in the User Admin? - django

Profile contains a PointField. I've used OSMGeoAdmin in the ProfileAdmin, here:
class ProfileAdmin(admin.OSMGeoAdmin):
model = Profile
But can't figure out how to use it in an inline for display in the UserAdmin. I currently have this set up as below:
# User Admin, with Profile attached
class ProfileInline(admin.StackedInline):
model = Profile
can_delete = False
verbose_name_plural = 'Profile' # As only one is displayed in this view
class UserAdmin(UserAdmin):
inlines = (
ProfileInline,
)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
Is it possible to use class OSMGeoAdmin in this situation?

This would be a good feature to request I guess.
As a workaround, you can take advantage of the fact that an InlineModelAdmin is quite similar to a ModelAdmin. Both extend BaseModelAdmin.
Inheriting from both StackedInline and ModelAdmin should not clash too much.
The only issue is that both __init__() methods take 2 positional arguments and call super().__init__() without arguments. So whatever the inheritance order, it will fail with TypeError: __init__() missing 2 required positional arguments: 'parent_model' and 'admin_site'
Fortunately, the InlineModelAdmin.__init__() method, the one we are interested in, is not really verbose nor complex (not too much super().__init__() calls in cascade).
Here is what it looks like in Django 1.9:
def __init__(self, parent_model, admin_site):
self.admin_site = admin_site
self.parent_model = parent_model
self.opts = self.model._meta
self.has_registered_model = admin_site.is_registered(self.model)
super(InlineModelAdmin, self).__init__()
if self.verbose_name is None:
self.verbose_name = self.model._meta.verbose_name
if self.verbose_name_plural is None:
self.verbose_name_plural = self.model._meta.verbose_name_plural
And here is what its parent (BaseModelAdmin) looks like in Django 1.9
def __init__(self):
overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
overrides.update(self.formfield_overrides)
self.formfield_overrides = overrides
Now let's put it all together:
from django.contrib.admin.options import FORMFIELD_FOR_DBFIELD_DEFAULTS
# User Admin, with Profile attached
class ProfileInline(OSMGeoAdmin, admin.StackedInline):
model = Profile
can_delete = False
verbose_name_plural = 'Profile' # As only one is displayed in this view
def __init__(self, parent_model, admin_site):
self.admin_site = admin_site
self.parent_model = parent_model
self.opts = self.model._meta
self.has_registered_model = admin_site.is_registered(self.model)
overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
overrides.update(self.formfield_overrides)
self.formfield_overrides = overrides
if self.verbose_name is None:
self.verbose_name = self.model._meta.verbose_name
if self.verbose_name_plural is None:
self.verbose_name_plural = self.model._meta.verbose_name_plural
class UserAdmin(UserAdmin):
inlines = (
ProfileInline,
)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
It's not really a satisfying solution as it requires to copy/paste some code from django, which may be different within the version of Django you use, and might be a pain to maintain when upgrading Django. However it should work until it is included in Django as a mix-in or as an InlineModelAdmin.
Note: the code snippets above are taken from Django 1.9, you should browse github tags to find the snippets corresponding to your version.

Since Django admin fields use widgets, you can override the widget that's automatically set for a PointField using formfield_overrides. In this case, you can override all PointField instances to use the OSMWidget class like so:
from django.contrib.gis.forms.widgets import OSMWidget
class ProfileInline(admin.StackedInline):
model = Profile
can_delete = False
verbose_name_plural = 'Profile' # As only one is displayed in this view
formfield_overrides = {
PointField: {"widget": OSMWidget},
}

Related

After update to DRF 3.0 and Django 1.8, Serialiser method invalid

I am not sure whether it is related or not. But before I update to DRF 3.0 and Django 1.8, my following code is working properly.
class DialogueSerializer(serializers.ModelSerializer):
sound_url = serializers.SerializerMethodField('get_sound_url')
class Meta:
model = Dialogue
fields = ('id','english','myanmar', 'sound_url')
def get_sound_url(self, dialogue):
if not dialogue.sound:
return None
request = self.context.get('request')
sound_url = dialogue.sound.url
return request.build_absolute_uri(sound_url)
Some people also said like this.
https://github.com/lightweightdjango/examples/issues/2
Now, when I run, I got
It is redundant to specify get_sound_url on SerializerMethodField
'sound_url' in serializer 'DialogueSerializer', because it is the same
as the default method name. Remove the method_name argument.
How shall I do?
Since your field is sound_url and your method get_field_name (get_sound_url), you don't have to give to SerializerMethodField the method name.
As you can see in the example from DRF documentation, there is no need to precise method_name in this case.
from django.contrib.auth.models import User
from django.utils.timezone import now
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
days_since_joined = serializers.SerializerMethodField()
class Meta:
model = User
def get_days_since_joined(self, obj):
return (now() - obj.date_joined).days
So, the result is :
class DialogueSerializer(serializers.ModelSerializer):
sound_url = serializers.SerializerMethodField() # no more `method_name` passed
class Meta:
model = Dialogue
fields = ('id','english','myanmar', 'sound_url')
def get_sound_url(self, dialogue):
if not dialogue.sound:
return None
request = self.context.get('request')
sound_url = dialogue.sound.url
return request.build_absolute_uri(sound_url)

django-autocomplete-light with User - choices should be a queryset

I can't seem to make django-autocomplete-light work with the django contrib user model. Always get exception 'choices should be a queryset'
This is my autocomplete class (defined in autocomplete_ligh_registry.py):
import autocomplete_light
from django.contrib.auth.models import User
class UserAutocomplete(autocomplete_light.AutocompleteModelBase):
search_fields = ['first_name']
model = User
autocomplete_light.register(UserAutocomplete)
my form (in forms.py):
class TransactionForm(forms.Form):
mymodel = forms.ModelChoiceField( required=True,
queryset=User.objects.all() ,
widget=autocomplete_light.ChoiceWidget('UserAutocomplete'))
When trying to render with {{form}}, it throws an exception: choices should be a queryset:
stack:
/home/prj/docs/projectos/.../src/autocomplete-light/autocomplete_light/widgets.py in render
choices = autocomplete.choices_for_values() ...
▶ Local vars
/home/prj/docs/projectos/.../src/autocomplete-light/autocomplete_light/autocomplete/model.py in choices_for_values
assert self.choices is not None, 'choices should be a queryset'
This is django 1.6 running in development. I have Users created.
django-autocomplete-light works ok with an autocompleteListBase, e.g.:
class OsAutocomplete(autocomplete_light.AutocompleteListBase):
choices = ['Linux', 'BSD', 'Minix']
autocomplete_light.register(OsAutocomplete)
so urls.py are including the registry, urls are registered and javascript is being loaded.
Following these docs:
http://django-autocomplete-light.readthedocs.org/en/latest/index.html#tutorial
Any pointers?
Thanks!
Hmm, got it... Docs don't mention but it needs choices to be explicitly defined on the autocomplete class.
class UserAutocomplete(autocomplete_light.AutocompleteModelBase):
search_fields = ['email']
choices = User.objects.all()
model = User
autocomplete_light.register(UserAutocomplete)

Override default queryset in Django admin

One of my models has a deleted flag, which is used to hide objects globally:
class NondeletedManager(models.Manager):
"""Returns only objects which haven't been deleted"""
def get_query_set(self):
return super(NondeletedManager, self).get_query_set().exclude(deleted=True)
class Conversation(BaseModel):
...
deleted = models.BooleanField(default=False)
objects = NondeletedManager()
all_conversations = models.Manager() # includes deleted conversations
How can I override the default queryset used by Django admin module to include deleted conversations?
You can override get_queryset method in your model admin class.
class MyModelAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(author=request.user)
Note in Django<=1.5 the method was named just queryset.
Konrad is correct, but this is more difficult than the example given in the documentation.
Deleted conversations can't be included in a queryset that already excludes them. So I don't see an option other than re-implementing admin.ModelAdmin.queryset entirely.
class ConversationAdmin (admin.ModelAdmin):
def queryset (self, request):
qs = Conversation.all_conversations
ordering = self.get_ordering(request)
if ordering:
qs = qs.order_by(*ordering)
return qs
You can do this with a Django proxy model.
# models.py
class UnfilteredConversation(Conversation):
class Meta:
proxy = True
# this will be the 'default manager' used in the Admin, and elsewhere
objects = models.Manager()
# admin.py
#admin.register(UnfilteredConversation)
class UnfilteredConversationAdmin(Conversation):
# regular ModelAdmin stuff here
...
Or, if you have an existing ModelAdmin class you want to re-use:
admin.site.register(UnfilteredConversation, ConversationAdmin)
This approach avoids issues that can arise with overriding the default manager on the original Conversation model - because the default manager is also used in ManyToMany relationships and reverse ForeignKey relationships.
What would be so wrong with the following:
class Conversation(BaseModel):
...
deleted = models.BooleanField(default=False)
objects = models.Manager() # includes deleted conversations
nondeleted_conversations = NondeletedManager()
So in your own apps/projects, you use Conversation.nondeleted_conversations() and let the built-in admin app do it's thing.
Natan Yellin is correct, but you can change the managers order and the first will be the default, then it is the used by the admin:
class Conversation(BaseModel):
...
deleted = models.BooleanField(default=False)
all_conversations = models.Manager() # includes deleted conversations
objects = NondeletedManager()
The admin implementation of get_queryset() use ._default_manager instead .objects, as show next
qs = self.model._default_manager.get_queryset()
ref Django github BaseModelAdmin implementation
This only ensures that every time you use YourModel.objects, you will not include deleted objects, but the generic views and others uses ._default_manager too. Then if you don't override get_queryset is not a solution. I've just check on a ListView and admin.
The accepted solution works great for me but I needed a little bit more flexibility, so I ended up extending the changelist view to add in a custom queryset parameter. I can now configure my default queryset/filter as such and it can still be modified by using a different filter (get parameters):
def changelist_view(self, request, extra_context=None):
if len(request.GET) == 0 :
q = request.GET.copy()
q['status__gt'] = 4
request.GET = q
request.META['QUERY_STRING'] = request.GET.urlencode()
return super(WorksheetAdmin,self).changelist_view(request, extra_context=extra_context)
To extend on some of these answers with what I found most concise and useful.
I've made the assumption you have a field like "name" to show the entries.
# admin.py
from django.contrib import admin
#admin.register(Conversation)
class ConversationAdmin(admin.ModelAdmin):
list_display = ('name', '_is_deleted')
# Nice to have but indicates that an object is deleted
#admin.display(
boolean=True,
ordering='deleted'
)
def _is_deleted(self, obj):
return obj.deleted
def get_queryset(self, request):
return Conversation.all_conversations
Which will give you an interface like:
The problem I found with subclassing a model was that it caused issues with meta inheritance and reverse-path lookups.

Django: Admin: changing the widget of the field in Admin

I have a model with a boolean value like that:
class TagCat(models.Model):
by_admin = models.BooleanField(default=True)
This appears as a checkbox in admin.
How could I use this as a radio button in admin?
Also, how do I make it be always with a certain selected value in admin?
Also, I want the default value to be the opposite, when a non-admin user adds a TagCat. This field should be hidden from him.
Can someone tell me how to do this? Django documentation doesn't seem to go in such details.
UPDATE 1: Code that gets me done with 1) (don't forget tot pass CHOICES to the BooleanField in the model)
from main.models import TagCat
from django.contrib import admin
from django import forms
class MyTagCatAdminForm(forms.ModelForm):
class Meta:
model = TagCat
widgets = {
'by_admin': forms.RadioSelect
}
fields = '__all__' # required for Django 3.x
class TagCatAdmin(admin.ModelAdmin):
form = MyTagCatAdminForm
admin.site.register(TagCat, TagCatAdmin)
The radio buttons appear ugly and displaced, but at least, they work
I solved with following info in MyModel.py:
BYADMIN_CHOICES = (
(1, "Yes"),
(0, "No"),
)
class TagCat(models.Model):
by_admin = models.BooleanField(choices=BYADMIN_CHOICES,default=1)
There is another way to do this that is, IMO much easier if you want every field of the same type to have the same widget. This is done by specifying a formfield_overrides to the ModelAdmin. For example:
from django.forms.widgets import Textarea
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = {
models.TextField: {'widget': Textarea},
}
More in the docs: https://docs.djangoproject.com/en/1.4/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_overrides
Here is a more dynamic extension of mgPePe's response:
class MyAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyAdminForm, self).__init__(*args, **kwargs)
self.fields['by_admin'].label = 'My new label'
self.fields['by_admin'].widget = forms.RadioSelect()
class Meta:
model = TagCat
class MyAdmin(admin.ModelAdmin):
fields = ['name', 'by_admin']
form = MyAdminForm
This way you get full control over the fields.

Django admin: How to display a field that is marked as editable=False' in the model?

Even though a field is marked as 'editable=False' in the model, I would like the admin page to display it. Currently it hides the field altogether.. How can this be achieved ?
Use Readonly Fields. Like so (for django >= 1.2):
class MyModelAdmin(admin.ModelAdmin):
readonly_fields=('first',)
Update
This solution is useful if you want to keep the field editable in Admin but non-editable everywhere else. If you want to keep the field non-editable throughout then #Till Backhaus' answer is the better option.
Original Answer
One way to do this would be to use a custom ModelForm in admin. This form can override the required field to make it editable. Thereby you retain editable=False everywhere else but Admin. For e.g. (tested with Django 1.2.3)
# models.py
class FooModel(models.Model):
first = models.CharField(max_length = 255, editable = False)
second = models.CharField(max_length = 255)
def __unicode__(self):
return "{0} {1}".format(self.first, self.second)
# admin.py
class CustomFooForm(forms.ModelForm):
first = forms.CharField()
class Meta:
model = FooModel
fields = ('second',)
class FooAdmin(admin.ModelAdmin):
form = CustomFooForm
admin.site.register(FooModel, FooAdmin)
Add the fields you want to display on your admin page.
Then add the fields you want to be read-only.
Your read-only fields must be in fields as well.
class MyModelAdmin(admin.ModelAdmin):
fields = ['title', 'author', 'published_date', 'updated_date', 'created_date']
readonly_fields = ('updated_date', 'created_date')
You could also set the readonly fields as editable=False in the model (django doc reference for editable here). And then in the Admin overriding the get_readonly_fields method.
# models.py
class MyModel(models.Model):
first = models.CharField(max_length=255, editable=False)
# admin.py
class MyModelAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
return [f.name for f in obj._meta.fields if not f.editable]
With the above solution I was able to display hidden fields for several objects but got an exception when trying to add a new object.
So I enhanced it like follows:
class HiddenFieldsAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
try:
return [f.name for f in obj._meta.fields if not f.editable]
except:
# if a new object is to be created the try clause will fail due to missing _meta.fields
return ""
And in the corresponding admin.py file I just had to import the new class and add it whenever registering a new model class
from django.contrib import admin
from .models import Example, HiddenFieldsAdmin
admin.site.register(Example, HiddenFieldsAdmin)
Now I can use it on every class with non-editable fields and so far I saw no unwanted side effects.
You can try this
#admin.register(AgentLinks)
class AgentLinksAdmin(admin.ModelAdmin):
readonly_fields = ('link', )