Django Grapelli add autocomplete lookups on InlineAdmin - python

I have this 3 models:
class MyFile(models.Model):
file = models.FileField(upload_to="files/%Y/%m/%d")
def __unicode__(self):
"""."""
return "%s" % (
self.file.name)
class ExampleModel(models.Model):
attached_files =models.ManyToManyField(MyFile)
main_model = models.ForeignKey(MainModel)
class MainModel(models.Model):
attached_files =models.ManyToManyField(MyFile)
And my admin.py as follows:
class ExampleModelAdminInline(admin.TabularInline):
model = ExampleModel
extra = 2
class MainModelAdmin(admin.ModelAdmin):
inlines = [ExampleModelAdminInline]
Im using django-grapelli because it offer autocomplete lookups for many to many fields. However, Im not sure how to add this autocomplete lookup to a TabularInline admin. Can anyone explain me how to set up the attached_files field to have autocomplete lookups?

First you need to set the static method autocomplete_search_fields() in the Model you want to search from, in your case MyFile. From the docs we get:
class MyFile(models.Model):
#your variable declaration...
#staticmethod
def autocomplete_search_fields():
return ("id__iexact", "name__icontains",) #the fields you want here
You can also define GRAPPELLI_AUTOCOMPLETE_SEARCH_FIELDS instead of declaring the static method, like:
GRAPPELLI_AUTOCOMPLETE_SEARCH_FIELDS = {
"myapp": {
"MyFile": ("id__iexact", "name__icontains",)
}
}
Then you should add the lookup and raw fields to your desired admin class, considering its related Model (say, your ExampleModel) which is the one that has a ManyToManyField. You can also handle ForeignKey in a similar way. Also from the mentioned docs:
class ExampleModel(models.Model):
main_model = models.ForeignKey(MainModel) #some FK to other Model related
attached_files =models.ManyToManyField(MyFile) #the one with static decl
class MainModelAdmin(admin.ModelAdmin):
#your variable declaration...
# define raw fields
raw_id_fields = ('main_model','attached_files',)
# define the autocomplete_lookup_fields
autocomplete_lookup_fields = {
'fk': ['main_model'],
'm2m': ['attached_files'],
}
Remember to register both ends (your models) of the relationship to your admin.site, like this:
#the one with the m2m and the one with the lookup
admin.site.register(ExampleModel, MainModelAdmin)
You can also check this question to understand better.

Related

MongoEngine (python) - Extending the document class not working

Im trying to extend the base functionality of the Document class like the following:
class DocumentExtended(Document):
meta = {'allow_inheritance': True}
class User(DocumentExtended):
name = StringField()
User(name="John Smith").save()
The purpose is that I want to add some extra methods to DocumentExtended (but I've omitted those for brevity)
The problem is that the document does not get saved.
If I do
class User(Document):
name = StringField()
User(name="John Smith").save()
it does get saved so I know it should work
Is there some weird trick I need to do to be able to extend the mongoengine.Document class and be able to save the models to the database?
After 2 hours of not understanding I finally read the docs
The DocumentExtended class must set meta = {'abstract': True}
class DocumentExtended(Document):
meta = { 'abstract': True }

Render fields conditionally with Django-Filters

I'm working on my Django SAAS app in which I want to allow the user to have some custom settings, like disable or enable certain filters. For that I'm using django-user-setttings combined with django-filters and simple forms with boolean fields:
class PropertyFilterSetting(forms.Form):
filter_by_loans = forms.BooleanField(required=False)
filter_by_tenants = forms.BooleanField(required=False)
The issue is that when trying to apply those settings, I keep running into serious spaghetti code:
views.py
class PropertyListView(LoginRequiredMixin, FilterView):
template_name = 'app/property_list.html'
context_object_name = 'properties'
def get_filterset_class(self):
print(get_user_setting('filter_by_tenants', request=self.request))
return PropertyFilterWithoutTenant if not get_user_setting('filter_by_tenants', request=self.request)['value'] else PropertyFilter
filter.py
class PropertyFilter(django_filter.FilterSet):
...
class PropertyFilterWithoutTenant(PropertyFilter):
...
and I'd have to do the same thing with the rest of the features. Is there any better way to implement this?
You can create methods in your User model, or a new class which acts as a store for all the methods. Each method will give you the relevant filterset class based on the value of corresponding user setting.
Something like:
class UserFilterset:
def __init__(self, request):
self.request = request
def get_property_filterset(self):
if not get_user_setting('filter_by_tenants', request=self.request)['value']:
return PropertyFilterWithoutTenant
return PropertyFilter
... # add more such methods for each user setting
Now you can use this method to get the relevant filterset class
class PropertyListView(LoginRequiredMixin, FilterView):
template_name = 'app/property_list.html'
context_object_name = 'properties'
def get_filterset_class(self):
return UserFilterset(self.request).get_property_filterset()
So even if in future you want to add some more logic, you can just update the relevant method, it would be cleaner and manageable.
I'm not sure how MVT stucture will exactly respond to this one but i use a custom generic class in REST structure to add custom filter fields in any viewset that i want
class ListAPIViewWithFilter(ListAPIView):
def get_kwargs_for_filtering(self):
filtering_kwargs = {}
if self.my_filter_fields is not []:
# iterate over the filter fields
for field in self.my_filter_fields:
# get the value of a field from request query parameter
field_value = self.request.query_params.get(field)
if field_value:
filtering_kwargs[field] = field_value
return filtering_kwargs
def get_queryset(self):
queryset = super(ListAPIViewWithFilter, self).get_queryset()
filtering_kwargs = self.get_kwargs_for_filtering()
if filtering_kwargs != {}:
# filter the queryset based on 'filtering_kwargs'
queryset = queryset.filter(**filtering_kwargs)
self.pagination_class = None
else:
return queryset
return queryset[:self.filter_results_number_limit]
changing origional get_queryset function in views.py should be the key to solving your problem (it works in django rest).
try checking what function gets the data then just identify the field wanted from it.

Quick way to add a class to a django admin field?

Is there a quicker way to append a class name to an input field in Django Admin? e.g. I have a text field called 'markdown', and I want to add the class markdown to it, so I can easily apply a markdown editor in admin.
The result of this code is perfect. Just a bit of a PITA to apply this across all my admin forms..
I can't use formfield_override since not all my textareas are markdown.
class MyModelAdminForm(forms.ModelForm):
class Meta:
model = models.MyModel
widgets = {
'markdown': forms.Textarea(attrs={'class': 'markdown'})
}
class MyModelAdmin(admin.ModelAdmin):
form = MyModelAdminForm
Why not creating a string which will hold the class name for each input you want to give it a class and then pass that string to attrs's class key?
# forms.py
MARKDOWN = 'markdown'
class MyModelAdminForm(forms.ModelForm):
class Meta:
model = models.MyModel
widgets = {
'markdown': forms.Textarea(attrs={'class': MARKDOWN})
}
Now you can reuse MARKDOWN to other modelForms. Of course this does not solve the repetition but at least is less error prone.
Another solution is to use javascript (first include it via the class Media inside your MyModelAdmin class) and then inside it you should do something like that:
// myModelAdmin.js
const $ = django.jQuery;
$(function() {
$('your-selectors').addClass('markdown');
});

django - add existing ModelAdmin with his inlines to another admin form

i have:
class BookAdmin(ModelAdmin):
inlines = [ TextInline,]
class EventAdmin(ModelAdmin):
pass
when viewing Event at admin, i want that BookAdmin will be shown at the same form(with his inlines)
is it possible?
thanks
If the point is just to show data from Book you may add Book fields in EventAdmin with __ notation (if the two models have some kind of relation) or just define EventAdmin methods that fetches values from Book and add them as readonly_fields.
Something like this:
class EventAdmin(ModelAdmin):
def book_texts(self, instance):
out = ''
for book in instance.books:
for inline in book.your_other_replated_class:
out += inline.value_to_print
return out
book_text.allow_tags = True
readonly_fields = [book_texts]
Otherwise, if the point is to be able to submit the two forms together, I'll suggest to define a custom Form class and handle the submitted data in a View.

How to post to a Django REST Framework API with Related Models

I have two related models (Events + Locations) with a serialzer shown below:
class Locations
title = models.CharField(max_length=250)
address = model.CharField(max_length=250)
class Events
title = models.CharField(max_length=250)
locations = models.ForeignKey(Locations, related_name='events'
class EventsSerializer(serializers.ModelSerializer):
class Meta:
model = Events
depth = 1
I set the depth to 1 in the serializer so I can get the information from the Locations model instead of a single id. When doing this however, I cant post to events with the location info. I can only perform a post with the title attribute. If I remove the depth option in the serializer, I can perform the post with both the title and location id.
I tried to create a second serializer (EventsSerialzerB) without the depth field with the intention of using the first one as a read-only response, however when I created a second serializer, viewset, and added it to the router, it would automatically override the original viewset.
Is it possible for me to create a serializer that outputs the related model fields, and allows you to post directly to the single model?
// EDIT - Here's what I'm trying to post
$scope.doClick = function (event) {
var test_data = {
title: 'Event Test',
content: 'Some test content here',
location: 2,
date: '2014-12-16T11:00:00Z'
}
// $resource.save() doesn't work?
$http.post('/api/events/', test_data).
success(function(data, status, headers, config) {
console.log('sucess', status);
}).
error(function(data, status, headers, config) {
console.log('error', status);
});
}
So when the serializers are flat, I can post all of these fields. The location field is the id of a location from the related Locations table. When they are nested, I can't include the location field in the test data.
By setting the depth option on the serializer, you are telling it to make any relation nested instead of flat. For the most part, nested serializers should be considered read-only by default, as they are buggy in Django REST Framework 2.4 and there are better ways to handle them in 3.0.
It sounds like you want a nested representation when reading, but a flat representation when writing. While this isn't recommended, as it means GET requests don't match PUT requests, it is possible to do this in a way to makes everyone happy.
In Django REST Framework 3.0, you can try the following to get what you want:
class LocationsSerializer(serializers.ModelSerializer):
class Meta:
model = Locations
fields = ('title', 'address', )
class EventsSerializer(serializers.ModelSerializer):
locations = LocationsSerializer(read_only=True)
class Meta:
model = Events
fields = ('locations', )
class EventViewSet(viewsets.ModelViewSet):
queryet = Event.objects.all()
serializer_class = EventsSerializer
def perform_create(self, serializer):
serializer.save(locations=self.request.data['locations'])
def perform_update(self, serializer):
serializer.save(locations=self.request.data['locations'])
A new LocationsSerializer was created, which will handle the read-only nested representation of the Locations object. By overriding perform_create and perform_update, we can pass in the location id that was passed in with the request body, so the location can still be updated.
Also, you should avoid having model names being plurals. It's confusing when Events.locations is a single location, even though Locations.events is a list of events for the location. Event.location and Location.events reads a bit more clearly, the Django admin will display them reasonably, and your fellow developers will be able to easily understand how the relations are set up.

Categories

Resources