How can I limit ImageField to a few choices in Django Admin - django

I have a model like with a file defined like
models.ImageField(upload_to='folder_icons', null=True)
I want to be able to limit the choice of this icon to a few pre created choices.
I there as way I can show the user (staff member) the choices in the django admin perhaps in a dropdown ?
This is similar to where I want a field where you choose between a few different avatars. Is there a custom field somewhere that can do this ?
Thanks

Just as a starting point, you would need to override the ModelAdmin.get_form() method, which will allow you to change the type of input field that Django uses by default for your image field. Here's what it should look like:
from django.forms import Select
class YourModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
# 1. Get the form from the parent class:
form = super(YourModelAdmin, self).get_form(request, obj, **kwargs)
# 2. Change the widget:
form.base_fields['your_image_field'].widget = Select(choices=(
('', 'No Image'),
('path/to/image1.jpg', 'Image 1'),
('path/to/image2.jpg, 'Image 2'),
))
# 3. Return the form!
return form
You'll still have some other considerations - for instance, the path/location of the images themselves (placing them in the settings.MEDIA_ROOT would probably be easiest, or at least the first step in trying to make this work). I can also imagine that you might want a more sophisticated presentation of this field, so that it shows a thumbnail of the actual images (see #Cheche's answer where he suggests select2 - that gets a bit more complicated though, as you'll need to craft a custom widget).
All of that said, in terms of just altering the form field that the admin uses so that it offers a dropdown/select field, rather than a file upload field, this is how you would achieve that.

What you need is a widget to render your choices. You could try with select2 or any django adapt to this, like django-select2. Check this.

Related

Django - How to call localflavor US_STATES in HTML

How do you make a drop down menu that contains all of the options in localflavor's US_STATES?
I can see how to create a model that contains a field that uses the localflavor option US_STATES.
class State(models.Model):
states = models.CharField(max_length=2, choices=US_STATES , null=True, blank=True)
The field state is then in a manytomany relationship to a model called Person. How do you take this a put it in a html page?
In my view, I can only think of doing this.
def get_context_data(self, *args, **kwargs):
context = super(UserProfileUpdateView, self).get_context_data(*args, **kwargs)
context['states'] = State.objects.all()
But this only pulls the existing state options.
1) How do I pull all states into the view?
2) How can I render an html template to use the output of 1? I imagine it has something to do with the 'choices' option, but I haven't ever done that before.
Thanks
You probably want to take a look at Django's forms system. Since what you're doing here is rendering a form (I'm guessing that by "drop down menu" you mean an HTML <select> element with all the states as options), that would be the preferred way to do it.
The localflavor package includes form field classes for working with its data types, including states.

How to add a custom button or link beside a form field

I am using Django with crispy_forms third party library. I want to add a link beside a form field like some forms in Django admin app. How can I do this?
You've picked quite a complicated example that uses a method I wouldn't even recommend. But I'll try to explain how you see what you're seeing, and keep it short.
That is a AdminTimeWidget(forms.TimeInput) and a AdminDateWidget both nested in a AdminSplitDateTime(SplitDateTimeWidget(MultiWidget)). The MultiWidget part of this isn't really important, that's just how you bind two widgets together to provide one value (a datetime.datetime).
Here's what AdminTimeWidget looks like:
class AdminTimeWidget(forms.TimeInput):
#property
def media(self):
extra = '' if settings.DEBUG else '.min'
js = [
'vendor/jquery/jquery%s.js' % extra,
'jquery.init.js',
'calendar.js',
'admin/DateTimeShortcuts.js',
]
return forms.Media(js=["admin/js/%s" % path for path in js])
def __init__(self, attrs=None, format=None):
final_attrs = {'class': 'vTimeField', 'size': '8'}
if attrs is not None:
final_attrs.update(attrs)
super().__init__(attrs=final_attrs, format=format)
That adds a DateTimeShortcuts.js script to the page (in the way that Admin Widgets can, via the form media property) and it's that script that iterates input tags looking for date and time inputs.
There's a LOT of machinery involved to get that happening but again, in effect, it's just a bit of javascript that looks for a date/time input and adds the HTML client-side.
But you probably don't want to do that.
As I said, that's a very complicated widget, and in Admin where it's harder to alter things on the fly. If you want to write an Admin widget, you probably do want to go that way.
But if you already control the template, or a crispy layout, you could just bung in some HTML. Crispy has an HTML element that you can throw into layouts. This is well documented.
Or if you want a reusable widget, you could use a custom template. Since Django 1.11, Widgets use templates to render.
Create a widget, borrowing from an existing one to save time
from django.forms import widgets
class DateWithButtonWidget(widgets.DateInput):
template_name = 'widgets/date_with_button.html'
Customise the template with the HTML you want:
{% include "django/forms/widgets/input.html" %} <button>MY BUTTON</button>
Use that widget in your form:
class MyForm(forms.ModelForm):
fancydate = forms.DateField(widget=DateWithButtonWidget)
Of course, wiring that button to do something is all up to you. Using a fully-scripted option might be what you need after all.

Make Django build contenteditable div for TextField instead of textarea

In my Django 1.10 project, I have a model:
class Contact(models.Model):
notes = models.TextField()
...and ModelForm:
class ContactForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
for field_name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control input-sm plain'
if field.required == True:
field.widget.attrs['required'] = ''
class Meta:
model = Contact
fields = ('notes',)
I have two questions regarding this:
Can I make Django render the notes field as div with contenteditable=true rather than textarea?
If yes, how do I automate the form.save() method?
The second question is a bit vague, so I would be grateful for a hint regarding the first question. I have read through the doc, but couldn't find relevant section :(
Question 1: Render a field with a specific HTML tag
In this case, <div contenteditable="true">...</div>
You can customize the widget used to render a form field. Basically, when you declare the field, you have to pass the argument widget=TheWidgetClass. Django has a lot of builtin widgets you can use. But you can also define your own. To know how, you will find many resources on the Internet. Simply search for "django create custom widget" on a search engine, or on SO. Possible answer: Django: How to build a custom form widget?
Since Django doesn't provide any official documentation on how to create custom widget, the smartest way would be to create a class inheriting Django's TextArea and using a custom html template based on original one.
Question 2: Automate form.save() method with such a custom widget
Basically, you have to make sure the value of the div tag is sent with other inputs values to the target view of your form, in POST data. In other words, you have to make sure the content of the div acts like the content of any form field.
Maybe this SO question can help you: one solution could be using JavaScript to copy the content of your div to a hidden textarea tag in the form when the user click on submit button.
Good luck ;)
First one can be done by using jquery. Since django will load the textarea with id='id_notes'. So after that you can select the element and do whatever you want to.
And in second one you can redefine the save method by writing you own save function in forms.py Here is an example for a url shortener.
Save method basically, defines what you want to execute when something is being committed to database.

How do I specify (1) an order and (2) a meaningful string representation for users in my Django application?

I have a Django application with users. I have a model called "Course" with a foreign key called "teacher" to the default User model that Django provides:
class Course(models.Model):
...
teacher = models.ForeignKey(User, related_name='courses_taught')
When I create a model form to edit information for individual courses, the possible users for the teacher field appear in this long select menu of user names.
These users are ordered by ID, which is of meager use to me. How can I
order these users by their last names?
change the string representation of the User class to be "Firstname Lastname (username)" instead of "username"?
Firsty, the order. You can define a default order of model using the nested Meta class. Check the ordering section.
Secondly, representation. You have to define a __str__()/__unicode__() methods for your model. They should return a string which represents an object. You can see an example in documentation. BUT, since User is a model from an outer module it may be hard to do it in that way.
You probably can:
monkey-patch the User model meta class during app initialization OR
subclass the User model, add Meta to the subclass and use it in place of the User OR
write a custom field / form template which uses objects attributes instead of calling str()
it all depends on the current case
Abstract
If you're only dealing with the admin, it's better not to tinker with the User model itself, as subclassing User can be a pain down the road (especially when / if other developpers are going to work on your project), but use the admin's customization options.
Solution
To solve your issue in the admin, you could use the ModelAdmin option raw_id_fields.
This will replace the <select> input with a widget that you can click to be redirected to your User admin and choose your user from.
From there, it's trivial to customize your User admin so that it:
Displays your users in a relevant order (ModelAdmin.ordering)
Displays the fields you're interest in (ModelAdmin.list_display)
Just remember than when you're registering your User admin, you need to use:
admin.site.unregister(User) #This!
admin.site.register(User, MyCustomUserAdmin)
Alternate solution
Alternatively, you can always use ModelAdmin.formfield_overrides, which is more powerful but more complicated.
Reference
You should look into the Django documentation for details on how to use those attributes.
You could achieve this by customizing form field, here the forms.ModelChoiceField
class CustomizedModelChoiceField(forms.ModelChoiceField):
def __init__(self, *args, **kwargs):
# change default ordering
super(CustomizedModelChoiceField, self).__init__(*args, **kwargs)
self.queryset = self.queryset.order_by('last_name')
def label_from_instance(self, obj):
# change representation per item
return u'{obj.first_name} {obj.last_name} ({obj.username})'.format(obj=obj)
Then use it in your form to replace default ModelChoiceField.
In Django Admin, it looks like
class CourseAdmin(admin.ModelAdmin):
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'teacher':
kwargs['form_class'] = CustomizedModelChoiceField
return super(CourseAdmin, self).formfield_for_dbfield(db_field, **kwargs)

Django Admin: Add text at runtime next to a field

I want to add a text next to a field of the django admin interface.
The warning needs to created at runtime inside a python method. I know python and the django ORM well, but I don't know how to get the text next the field.
The text should be a warning. Raising ValidationError in clean() is not a solution, since
the user can't edit the page any more. It should be just a warning message.
You can use custom ModelForm subclass for the admin, adding help_text attribute for the field in question at its initialization, and style it appropriately.
# forms.py
class YourModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(YourModelForm, self).__init__(*args, **kwargs)
self.fields['field_in_question'].help_text = generate_warning()
# admin.py
class YourModelAdmin(admin.ModelAdmin):
form = forms.YourModelForm
# And here you can specify custom CSS / JS which would make
# `help_text` for that particular field look like a warning.
# Or you can make it generic--say, style (and maybe reposition w/js) all tags
# like <span class="warning"> that occur within the help text of any field.
class Media:
css = {"all": ("admin_warning.css", )}
js = ("admin_warning.js", )
If you want to do it in changelist view, you can write in model method, which returns string in format you want, and put name of that method in list_display in admin.
class MyModel(models.Model):
myfield = models.CharField(max_length=100)
def myfield_with_warning(self):
return '%s - %s' % (self.myfield, '<span class="warn">My warning message</p>'
myfield_with_warning.short_description = 'My field verbose name'
myfield_with_warning.allow_tags = True
class MyModelAdmin(models.ModelAdmin):
list_display = ('myfield_with_warning',)
If it's not what you need, write more precisely, where do you want to display warning message.
I think the simplest way would be to override the specific admin page for that model. This is described here in the Django documentation. The template you need to override is probably change_form.html. Within these template displayed object is available in the template variable original.
I would add a method or property to you model, that generates and returns the error message and call this method from the template.
Edit: Have a look at contrib/admin/templates/admin/change_form.html there is a include for includes/fieldset.html that displays the the fields of the admin site. You could put some code there that chckes if the model has some special named attribute and if so it is displayed. You could them simply override that change_form.html for all models with your custom one.