Search code examples
django-adminpython-2.7django-widget

How can I extend a django admin widget so it's still optional?


I've been scouring the internet for a couple days and couldn't find anything, so I'm hoping you guys can point me in the right direction. I'm trying to customize the django admin so that a button appears inline after a URL field. The button appears, and the javascript works, except despite marking it null=True and blank=True the admin validation keeps saying the field is required; I want the url field to be optional.

Is there any way to make this field optional? I'm assuming it's some combination of blank=True and null=True, but I've tried it in a handful of places with no luck.

Here are what I think to be the relevant code bits (also, I know inline CSS from the widget is a bad idea. It's only until I get everything working!). If you need to see anything else, please let me know.

models.py

class Team(models.Model):
    name = models.CharField(max_length=64)
    name_color = models.CharField(max_length=7, default='#000000')
    name_shadow_color = models.CharField(max_length=7, default='#ffffff')
    created = models.DateField(editable=True, default=datetime.now)
    retired = models.DateField(null=True, blank=True)
    url = models.URLField(null=True, blank=True, default=None)

admin.py

class TeamAdmin(admin.ModelAdmin):
    list_filter = ('created', 'retired',)
    list_select_related = True
    list_display = ('name', 'created',)
    search_fields = ('name', )
    ordering = ('name',)
    form = TeamAdminForm

admin_forms.py

class TeamAdminForm(forms.ModelForm):
    url = URLActionField()

    class Media:
        js = ('js/jquery-1.8.0.min.js', 'js/admin/teamform.js', )

    class Meta:
        model = Team

admin_widgets.py

class URLActionField(forms.TextInput):
    def render(self, name, value, attrs=None):
        if attrs is None:
            attrs = {}

        # TODO: not responsive!!
        if 'style' not in attrs.keys():
            attrs['style'] = 'width: 275px;'
        else:
            attrs['style'] = '%s width: 275px;' % attrs['style']

        attrs['required'] = False
        attrs['blank'] = True
        attrs['null'] = True

        output = []
        output.append(super(URLActionField, self).render(name, value, attrs))
        output.append(' <input type="button" value="%s" style="width: 200px; margin-left: 20px; height: 24px; line-height: 15px;" class="grp-button" id="url-scraper">' % unicode(_(u'Scrape URL for data')))
        return mark_safe(u''.join(output))

Thanks in advance.


Solution

  • You need to make a custom widget and use it with the build-in URL field. You are dealing with a formfield and not a modelfield. So use 'required=False'. Avoid using null on string-based fields unless you have an excellent reason. If a string-based field has null=True, that means it has two possible values for “no data”: NULL, and the empty string.

    In Model.py:

    class Team(models.Model):
        ...
        url = models.URLField(blank=True)
    

    In Admin.py append to the build-in AdminURLFieldWidget output (no js required):

    from django.contrib.admin.widgets import AdminURLFieldWidget
    
    class CustomAdminURLFieldWidget(AdminURLFieldWidget):
        def render(self, name, value, attrs=None):
            output = []
            output.append(super(CustomAdminURLFieldWidget, 
                self).render(name, value, attrs))
            if value:
                output.append('<p><a href="%s">%s</a></p>' %(value, value))
            return mark_safe(u''.join(output))
    

    In Admin.py create a form:

    from models import Team
    
    class TeamAdminForm(forms.ModelForm):
            url = forms.URLField(required=False, widget=CustomAdminURLFieldWidget)
            class Meta:
                model = Team
    

    In Admin.py create a ModelAdmin:

    class TeamAdmin(admin.ModelAdmin):
        form = TeamAdminForm