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');
});
Related
I am programming a website builder in Django, and each page has lots of fields to fill out.
Some of them are pretty arcane, and to keep from cluttering up the page they are initially hidden:
class ScriptInlinePage(admin.TabularInline):
model = Page.script.through
extra = 0
fields = ('active', 'script', 'order', )
verbose_name = "script set"
verbose_name_plural = "script sets"
classes = ['collapse']
In the interests of streamlining the page, I have made it so that collapsed inlines are unobtrusive:
Script Sets (Show ▶)
However, these initially-hidden fields can have a disastrous effect if they contain a value and the user is unaware of it.
I am looking for a way to either:
add a class collapsed but initially visible if not empty, or
modify the collapse class so that it is only initially collapsed if it's empty
I have tried adding to models.py something like:
def is_empty:
if self.count > 0: return True
else: return False
but I don't know how to use this information in the Admin class to get the effect I want.
Similar question: I thought I saw a way to make an inline collapsible without making it initially collapsed, but after much googling I can't find it. Is that not a thing?
Thanks to A D who got me looking in the right place, I ended up copying the admin javascript that collapses the collapsed elements.
[app folder]/static/admin/js/ifempty.js
'use strict';
{
window.addEventListener('load', function() {
const fieldsets = document.querySelectorAll('fieldset.ifempty');
for (const [i, elem] of fieldsets.entries()) {
if (elem.querySelectorAll('div.related-widget-wrapper').length > 1) {
elem.classList.remove('collapse');
}
}
});
}
Then, in admin.py:
class PageAdmin(admin.ModelAdmin):
class Media:
js = ( 'admin/js/ifempty.js', )
...
The result is that in the inline, I can add ifempty to the class list:
class ScriptInlinePage(admin.TabularInline):
model = Page.script.through
extra = 0
fields = ('active', 'script', 'order', )
verbose_name = "script set"
verbose_name_plural = "script sets"
classes = ['collapse', 'ifempty',]
And when the page is loaded, "hidden" fields that have values are no longer hidden.
Initially I would have liked to make these fields collapsible, but I realized that users would never bother.
So, canceling the collapse class when the field contains data is a perfect solution.
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 }
I'm wondering if there is a way in Wagtail to enter a custom template path via CharField in a base model, and then establish a template in an inherited model that would be the default. For example:
base/models.py
class WebPage(Page):
template_path = models.CharField()
def get_template(self, request):
if self.template_path:
template = template_path
else:
template = *something*
app/models.py
class MyWebPage(WebPage):
template = 'default_template.html'
Ideally, I'd establish the template attribute in the MyWebPage model, and that would act as a default. However, the get_template method in the WebPage base model would supersede it, but only if it's not empty. Is any of this possible?
I was reading through the Wagtail Docs and found this page (http://docs.wagtail.io/en/v2.1.1/advanced_topics/third_party_tutorials.html) and on that page was an article about dynamic templating. This is the page that has it: https://www.coactivate.org/projects/ejucovy/blog/2014/05/10/wagtail-notes-dynamic-templates-per-page/
The idea is to set a CharField and let the user select their template. In the following example they're using a drop down, which might even be better for you.
class CustomPage(Page):
template_string = models.CharField(max_length=255, choices=(
(”myapp/default.html”, “Default Template”),
(”myapp/three_column.html”, “Three Column Template”,
(”myapp/minimal.html”, “Minimal Template”)))
#property
def template(self):
return self.template_string
^ code is from the coactivate.org website, it's not mine to take credit for.
In the template property, you could check if not self.template_string: and set your default path in there.
Edit #1:
Adding Page inheritance.
You can add a parent Page (the Base class) and modify that, then extend any other class with your new Base class. Here's an example:
class BasePage(Page):
"""This is your base Page class. It inherits from Page, and children can extend from this."""
template_string = models.CharField(max_length=255, choices=(
(”myapp/default.html”, “Default Template”),
(”myapp/three_column.html”, “Three Column Template”,
(”myapp/minimal.html”, “Minimal Template”)))
#property
def template(self):
return self.template_string
class CustomPage(BasePage):
"""Your new custom Page."""
#property
def template(self):
"""Overwrite this property."""
return self.template_string
Additionally, you could set the BasePage to be an abstract class so your migrations don't create a database table for BasePage (if it's only used for inheritance)
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.
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.