I'm using django admin + grappelli + tinymce from grappelli.
It works perfect, but I can't figure out how to make an admin form with textareas and apply tinymce only for part of them.
For example, I've two fields: description and meta-description. I need tinymce only for description and I need textarea without tinymce for meta-descrption.
tinymce in admin is enabled via form like this:
class AdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
"""Sets the list of tags to be a string"""
instance = kwargs.get('instance', None)
if instance:
init = kwargs.get('initial', {})
kwargs['initial'] = init
super(AdminForm, self).__init__(*args, **kwargs)
class Media:
js = (
settings.STATIC_URL + 'grappelli/tinymce/jscripts/tiny_mce/tiny_mce.js',
settings.STATIC_URL + 'grappelli/tinymce_setup/tinymce_setup.js',
)
and enabled in admin.py:
class ModelAdmin(admin.ModelAdmin):
form = AdminForm
It looks like behaviour is described in the beginning of tinymce init:
tinyMCE.init({
mode: 'textareas',
theme: 'advanced',
skin: 'grappelli',
...
Is there a way to solve my issue?
By setting the mode to textareas, it won't give you any sort of selector control as to which one it applies the editor to. You'll most likely need to drop the mode setting and go with selector: http://www.tinymce.com/wiki.php/Configuration:selector
The django-tinymce config options are just a mirror for TinyMCE's settings.
On a field by field basis you can use this technique providing a custom ModelForm:
class XAdminForm(forms.ModelForm):
name = forms.CharField(label='Name', max_length=100,
widget=forms.TextInput(attrs={'size': '100'}))
something = forms.CharField(label='Something', max_length=SOME_MAX_LENGTH,
widget=forms.Textarea(attrs={'rows': '10', 'cols': '100'}))
note = forms.CharField(label='Note', max_length=NOTE_MAX_LENGTH,
widget=forms.Textarea(attrs={'class': 'ckeditor'}))
class Meta:
model = x
class Meta:
model = x
class XAdmin(admin.ModelAdmin):
model = X
form = XAdminForm
class Media:
js = ('/static/js/ckeditor/ckeditor.js',)
admin.site.register(X, XAdmin)
Related
I have a Django input slider defined as follows:
#widgets.py
from django.forms.widgets import NumberInput
class RangeInput(NumberInput):
input_type = 'range'
#forms.py
from polls.widgets import RangeInput
class VoteForm(forms.ModelForm):
class Meta:
#model = CC_Responses
model = CC_Resp_NoFK
fields = ['Person_ID', 'Test_date', 'Response_value']
# following added from SliderModelForm
widgets ={
'Response_value':RangeInput
}
which is “processed” by the view
def p2vote(request,q_id):
CC_question = get_object_or_404(CC_Questions, pk=q_id)
#
if request.method == 'POST':
form = VoteForm(request.POST)
if form.is_valid():
item = form.save(commit=False)
item.Q_ID = q_id
item.save()
return redirect('/polls/p2')
else:
formV = VoteForm()
return render(request, 'pollapp2/vote.html', {'var_name':CC_question,'form' : VoteForm()})
and in the template I have the inline CSS
<style>
/* Following CSS is used by the input slider (range) since Django assigns id value of id_Response_value */
/*See: https://stackoverflow.com/questions/110378/change-the-width-of-form-elements-created-with-modelform-in-django */
#id_Response_value{width:300px;}
</style>
which is associated with the slider/range
<label for="Response_value">Response value</label>
{% render_field form.Response_value rows="1" class="form-control" %}
I obtained id_Response_value by looking at the source of the rendered HTML in the browser – I guess I could explicitly set an ID. All the above works exactly as I want. I can control the width of the range slider using CSS.
Now, I believe, inline CSS is a “bad thing”, so as a step to improve my code I’m trying to associate the attribute “more directly” with the slider.
In Django documentation in the section: https://docs.djangoproject.com/en/4.0/topics/forms/modelforms/#overriding-the-default-fields
there is the following example:
from django.forms import ModelForm, Textarea
from myapp.models import Author
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ('name', 'title', 'birth_date')
widgets = {
'name': Textarea(attrs={'cols': 80, 'rows': 20}),
}
The dictionary attrs created looks like it has CSS “stuff” in it, so I coded in widgets in the model form in forms.py
from polls.widgets import RangeInput
class VoteForm(forms.ModelForm):
class Meta:
model = CC_Resp_NoFK
fields = ['Person_ID', 'Test_date', 'Response_value']
widgets ={
'Response_value':RangeInput(attrs={'width': '1000px'}),
}
I tried both:
attrs={'width': '1000px'}
and
attrs={'width': 1000}
Neither changed the width of the range slider. Is what I am doing possible in the (model)form? Have I just got a problem with what I am coding in attrs?
I may have misunderstood but I see something like the following promoted as what needs to go in the ModelForm
def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs) # Call to ModelForm constructor
self.fields['long_desc'].widget.attrs['cols'] = 10
self.fields['long_desc'].widget.attrs['rows'] = 20
No. Really? To change the attributes?
I have main django admin class that includes inline class with two fields pos_x and pos_y.
I want to update this fields via JS and modal-window.
Can I add button in InlineClass without changing any admin html?
class InlineClass(CommonInlineConfigurationMixin):
model = MapPoint
fields = ['title', ('pos_x', 'pos_y')]
form = Form
I guess you can do it like this:
class InlineClass(CommonInlineConfigurationMixin):
model = MapPoint
fields = ['title', ('pos_x', 'pos_y'), 'button']
readonly_fields = ("button",)
form = Form
class Media:
js = ("js/my_code.js",)
def button (self, obj):
return "<button type='button' onclick='my_code()'>Click Me!</button>"
button.allow_tags=True
And put the my_code.js into your "static/js" folder.
Using ger.s.brett answer i have to format html in return, like this:
def button (self, obj):
return format_html("<button type='button' onclick='my_code()'>Click Me!</button>")
I try customize django comments module (delete field url)
i create empty class VSComments and form
from django import forms
from django.contrib.comments.forms import CommentForm
from vs_comments.models import VSComment
class VSCommentForm(CommentForm):
"""
No url Form
"""
VSCommentForm.base_fields.pop('url')
__init__
from vs_comments.models import VSComment
from vs_comments.forms import VSCommentForm
def get_model():
return VSComment
def get_form():
return VSCommentForm
also url(r'^comments/', include('django.contrib.comments.urls')),
include 'vs_comments' and 'django.contrib.comments' into INSTALLED_APPS and COMMENTS_APP = 'vs_comments'
As result, I have right form, without url field, but posting comments doesn't work
soution add to the form class
def get_comment_create_data(self):
# Use the data of the superclass, and remove extra fields
return dict(
content_type = ContentType.objects.get_for_model(self.target_object),
object_pk = force_unicode(self.target_object._get_pk_val()),
comment = self.cleaned_data["comment"],
name = self.cleaned_data["name"],
submit_date = datetime.datetime.now(),
site_id = settings.SITE_ID,
is_public = True,
is_removed = False,
)
For admin panel
class VSCommentAdmin(CommentsAdmin):
"""
all like native comments
"""
admin.site.register(Comment, CommentsAdmin)
But now didn't work tags render_comment_list and other. No any errors, only empty result
How can I fix it?
Do you have any error trace ?
I think it doesn't work because the validation of your VSCommentForm uses the validator of CommentForm, and the "url" field is missing in your customized form.
You should read more on customized form that inherits from another form and its validation :
https://docs.djangoproject.com/en/dev/topics/forms/#processing-the-data-from-a-form
https://code.djangoproject.com/wiki/CustomFormFields
https://docs.djangoproject.com/en/dev/ref/forms/validation/
the situation
In my example I want to create a Page model with a many to many relationship with a content-blocks model.
A page has a title, slug, and main content block.
content blocks have a title and a content block.
What I can get:
Showing page.blocks in the admin form displays a multi select of content blocks
Creating an inline form for the content blocks on the page admin shows several selects with a + sign to add more
What I am trying to accomplish:
Full CRUD on content block on the page admin
Note: Due to the difficulty of my request, I'm beginning to believe the UX pattern im trying to accomplish is wrong. If I want a content creator to come in and create a page, pick some existing content blocks (ex: an existing sidebar content block), and then create a new custom block. I don't think i want him to have to jump all over the place to do this...
Related Question without solutions:
How do I use a TabularInline with editable fields on a ManyToMany relationship?
EDIT
my admin.py
from django.contrib import admin
from django.contrib.flatpages.admin import FlatpageForm, FlatPageAdmin
from django.contrib.flatpages.models import FlatPage
from my_flatpages.models import ExtendedFlatPage, ContentBlock
from mptt.admin import MPTTModelAdmin
from django import forms
import settings
"""
Extended Flatpage Form
"""
class ExtendedFlatPageForm(FlatpageForm):
class Meta:
model = ExtendedFlatPage
"""
Page Content Block inline form
"""
class ContentBlockInlineAdminForm(forms.ModelForm):
# Add form field for selecting an existing content block
content_block_choices = [('', 'New...')]
content_block_choices.extend([(c.id, c) for c in ContentBlock.objects.all()])
content_blocks = forms.ChoiceField(choices=content_block_choices, label='Content Block')
def __init(self, *args, **kwargs):
super(ContentBlockInlineAdminForm, self).__init__(*args, **kwargs)
# Show as existing content block if it already exists
if self.instance.pk:
self.fields['content_block'].initial = self.instance.pk
self.fields['title'].initial = ''
self.fields['content'].initial = ''
# Make title and content not required so user can opt to select existing content block
self.fields['title'].required = False
self.fields['content'].required = False
def clean(self):
content_block = self.cleaned_data.get('content_block')
title = self.cleaned_data.get('title')
content = self.cleaned_data.get('content')
# Validate that either user has selected existing content block or entered info for new content block
if not content_block and not title and not content:
raise forms.ValidationError('You must either select an existing content block or enter the title and content for a new content block')
"""
Content Block Inline Admin
"""
class ContentBlockInlineAdmin(admin.TabularInline):
form = ContentBlockInlineAdminForm
class Meta:
model = ContentBlock
extra = 1
"""
Extended Flatpage Admin
"""
class ExtendedFlatPageAdmin(FlatPageAdmin, MPTTModelAdmin):
form = ExtendedFlatPageForm
fieldsets = (
(
None,
{
'fields': ('url', 'title', 'content', ('parent', 'sites'))
}
),
(
'SEO Fields',
{
'fields': ('seo_title', 'seo_keywords', 'seo_description'),
'classes': ('collapse', )
}
),
(
'Advanced options',
{
'fields': ('enable_comments', 'registration_required', 'template_name'),
'classes': ('collapse', )
}
),
)
inlines = (ContentBlockInlineAdmin,)
class Media:
js = (
'https://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js',
settings.MEDIA_URL + 'js/tinymce/jquery.tinymce.js',
settings.MEDIA_URL + 'js/init_tinymce.js'
)
admin.site.unregister(FlatPage)
admin.site.register(ExtendedFlatPage, ExtendedFlatPageAdmin)
Haven't had the opportunity to test this, but it should work:
class ContentBlockInlineAdminForm(forms.ModelForm):
# Add form field for selecting an existing content block
content_block_choices = [('', 'New...')]
content_block_choices.extend([(c.id, c) for c in ContentBlock.objects.all()])
content_blocks = forms.ChoiceField(choices=content_block_choices, label='Content Block')
def __init(self, *args, **kwargs):
super(ContentBlockInlineAdminForm, self).__init__(*args, **kwargs)
# Show as existing content block if it already exists
if self.instance.pk:
self.fields['content_block'].initial = self.instance.pk
self.fields['title'].initial = ''
self.fields['content'].initial = ''
# Make title and content not required so user can opt to select existing content block
self.fields['title'].required = False
self.fields['content'].required = False
def clean(self):
content_block = self.cleaned_data.get('content_block')
title = self.cleaned_data.get('title')
content = self.cleaned_data.get('content')
# Validate that either user has selected existing content block or entered info for new content block
if not content_block and not title and not content:
raise forms.ValidationError('You must either select an existing content block or enter the title and content for a new content block')
class ContentBlockInlineAdmin(admin.TabularInline):
form = ContentBlockInlineAdminForm
class Meta:
model = ContentBlock
extra = 1
class PageAdmin(admin.ModelAdmin):
inlines = [
ContentBlockInlineAdmin,
]
"""
Override saving of formset so that if a form has an existing content block selected, it
sets the form instance to have the pk of that existing object (resulting in update rather
than create). Also need to set all the fields on ContentType so the update doesn't change
the existing obj.
"""
def save_formset(self, request, form, formset, change):
for form in formset:
if form.cleaned_data.get('content_block'):
content_block = ContentBlock.objects.get(pk=form.cleaned_data.get('content_block'))
instance = form.save(commit=False)
instance.pk = content_block.pk
instance.title = content_block.title
instance.content = content_block.content
instance.save()
else:
form.save()
You could then actually add some javascript to show/hide the ContentBlock fields depending on whether the content_block field is set to 'New..' or an existing one.
This isn't the answer I was looking for, BUT, What I ended up going with is
class Page(models.Model):
....
class ContentBlock(models.Model):
page = models.ForeignKey(
Page,
blank = True,
null = True,
)
....
and then having a regular tabular inline for ContentBlock on the page admin form.
So that way I can have page specific content blocks related to a page, AND be able to have generic content blocks able to be used wherever.
Then, I created an inclusion tag to render a content block by name that I use in my templates.
The project https://github.com/caktus/django-pagelets sounds like exactly what you are looking for. A page can have 'pagelets' and 'shared pagelets' with a nice admin for the two (pagelets are simply content blocks).
The non-shared pagelets are shown as inlines with the ability to add extra blocks directly on the page admin screen. For shared pagelets you get the drop-down with a plus-sign.
I want to change the way that the "+" icon for the foreign key in the admin site is shown.
I found that the widget that prints the code is RelatedFieldWidgetWrapper that is in django/contrib/admin/widgets.py.
So I wrote my version of this class and I changed its render function.
But now how can I use it? I mean... in the definition of my model do I have to use the formfield_overrides in this way?
formfield_overrides = {
models.ForeignKey: {'widget': customRelatedFieldWidgetWrapper},
}
I think that this is not the right way, because that widget is not the one that manage the whole foreign key, but only the "+" icon.
Am I wrong?
Thanks a lot.
You would need to create custom ModelForm for ModelAdmin and override widget there.
Example code:
#forms.py
class CustomForm(forms.ModelForm):
user = forms.ModelChoiceField(queryset=User.objects.all(), widget=yourCustomWidget)
class Meta:
model = MyModel
#admin.py
class MyModelAdmin(admin.ModelAdmin):
form = CustomForm
I approached this slightly differently by monkey-patching the widget - that way the change is reflected in all forms and you're not monkeying around with django's source code.
I ran into this as I was working on customizing yawd admin, a very nice Twitter-Bootstrap skin for admin interface. Now all my icons are jazzed up.
import django.contrib.admin.widgets
class MyRelatedFieldWidgetWrapper(django.contrib.admin.widgets.RelatedFieldWidgetWrapper):
"""
This class is a wrapper to a given widget to add the add icon for the
admin interface.
"""
def render(self, name, value, *args, **kwargs):
rel_to = self.rel.to
info = (rel_to._meta.app_label, rel_to._meta.model_name)
self.widget.choices = self.choices
output = [self.widget.render(name, value, *args, **kwargs)]
if self.can_add_related:
related_url = reverse(
'admin:%s_%s_add'
% info, current_app=self.admin_site.name
)
output.append(
"""
<a href="%s"
onclick="return showAddAnotherPopup(this);
alt="%s">
<i class="help icon-large icon-plus-sign"
id="add_id_%s"
data-original-title>
</i>
</a>""" % (related_url, _('Add Another'), name))
return mark_safe(''.join(output))
# Monkeypatch it
django.contrib.admin.widgets.RelatedFieldWidgetWrapper = MyRelatedFieldWidgetWrapper