Django Admin custom foreign key select box - django

I want to customize Django admin select box and show thumbnail in the select box next to the image title
I have a class called Image and another class called News, that has a foreign key to the Image.
Note: I use Django jet as admin template.
class Image(models.Model):
alternate = models.CharField(
verbose_name=_('Alternate'),
max_length=255,
null=True,
blank=True
)
title = models.CharField(
verbose_name=_('Title'),
max_length=255,
null=True,
blank=True
)
artist = models.ManyToManyField(
'Artist',
verbose_name=_('Artist'),
blank=True
)
image = models.ImageField()
def __str__(self):
return "({}) {}".format(self.pk, self.title)
class Meta:
verbose_name = _('Image Attachment')
verbose_name_plural = _('Image Attachments')
#staticmethod
def autocomplete_search_fields():
return 'title',
class News(BaseModel):
title = models.CharField(
verbose_name=_('Title'),
max_length=255,
null=True,
blank=True
)
summery = RichTextField(
verbose_name=_('Summery'),
null=True,
blank=True,
)
main_image = models.ForeignKey(
Image,
verbose_name=_('Main Image'),
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='images'
)
Now I want to show the thumbnail of the image in choices in Django admin when I want to add news.
Now my select box look like this

You will need to create a custom widget that inherits from Select, the most important part it seems will be setting the option_template_name to be a template that you create to show the image. Since you are using something other than the base Django Admin, you may want to look into extending the widgets in that Library.
Something along the lines of:
class SelectWithImage(Select):
...
option_template_name = 'myapp/forms/widgets/select_option_with_image.html'
...
Then adjust the admin formfield_overrides for the News model in your admin.py as described here and you should be good to go!
This step will look something like this:
from django.contrib import admin
from django.db import models
# Import our custom widget and our model from where they're defined
from myapp.models import News
from myapp.widgets import SelectWithImage
class NewsAdmin(admin.ModelAdmin):
formfield_overrides = {
models.ForeignKey: {'widget': SelectWithImage},
}

Related

Django - Linking Models

I am using django and I want to link two models. The first model is comment and the second model is image. I want to have multiple images for one comment and an image should be linked with only one comment.
Comment model has its fields and image model looks like this:
class Image(models.Model):
image = models.ImageField(upload_to=f'{hash(id)}/', null=True, blank=True)
def __str__(self):
return self.image.name
And this is the model that I used to link comment and image:
class CommentImage(models.Model):
comment = models.OneToOneField(Comment, models.CASCADE, null=True)
image = models.ForeignKey(Image, models.CASCADE, null=True)
class Meta:
ordering = ["-id"]
verbose_name = _("Image")
verbose_name_plural = _("Images")
def __str__(self):
return self.image.image.name
Here is the admin panel of django:
enter image description here
As you can see I could be able add only one image and there is no button as well to add multiple image. What should I change to be able to add multiple images?
I have tried using ManytoManyField and removing comment field from CommentImage but it did not work.
I think you are overcomplicating things. Why not just add a text field to your ImageComment:
class Image(models.Model):
def upload_to(self, filename):
return f'{hash(self)}/{filename}'
image = models.ImageField(upload_to=upload_to, null=True, blank=True)
comment = models.ForeignKey(
'ImageComment', on_delete=models.SET_NULL, null=True
)
def __str__(self):
return self.image.name
class CommentImage(models.Model):
comment = models.TextField()
Or in case an Image can have multiple ImageComments as well, use a ManyToManyField:
class Image(models.Model):
def upload_to(self, filename):
return f'{hash(self)}/{filename}'
image = models.ImageField(upload_to=upload_to, null=True, blank=True)
comments = models.ManyToManyField(
'ImageComment'
)
def __str__(self):
return self.image.name
class CommentImage(models.Model):
comment = models.TextField()
You can even add an InlineModelAdmin to make editing comments at the same location as the image possible:
from django.contrib import admin
class ImageCommentInline(admin.TabularInline):
model = ImageComment
#admin.site.register(Image)
class ImageAdmin(admin.ModelAdmin):
inlines = [
ImageCommentInline,
]
You can try using this via manytomanyfields:
class CommentImage(models.Model):
comment = models.ForeignKey(Comment, models.CASCADE)
image = models.ForeignKey(Image, models.CASCADE)
class Comment(models.Model):
# other fields here
images = models.ManyToManyField(Image, through='CommentImage', related_name='comments')

i want to directly input in django admin dropdown box

This situation is like as above pic.
i want to direct input in dropdown
i mean, I want to make it possible to input directly, including choosing a dropdown.
enter image description here
# models.py
class AccountBook(TimeStampedModel):
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, null=False)
accountclassification = models.ForeignKey(AccountClassification, on_delete=models.CASCADE, null=True)
accountcategory = ChainedForeignKey(
"AccountCategory",
chained_field="accountclassification",
chained_model_field="accountclassification",
show_all=False,
auto_choose=True,
null=True
)
...
# admin.py
#admin.register(AccountBook)
class AccountBookAdmin(admin.ModelAdmin):
list_display = (
"accountclassification",
"accountcategory",
"account_amount",
"account_reference",
"account_manager",
"account_recoder"
)
date_hierarchy = 'created_time'
class Media:
js = (
'smart-selects/admin/js/chainedfk.js',
'smart-selects/admin/js/chainedm2m.js',
)

Django model select between two columns/fields?

I have a Slider module that i want to include items from movies_movie and shows_show table. An item can either be a show or movie. How do i make user select between movie and show? Currently i have columns for movie and show but how do i force user to select between the two?
also title_en is a column in movie or tv show tables. So the title of the movie/show selected should display in row after save.
class Slider_items(models.Model):
order = models.IntegerField(max_length=3, blank=True)
movie = models.ForeignKey('movies.movie', on_delete=models.CASCADE, blank=True)
show = models.ForeignKey('shows.show', on_delete=models.CASCADE, blank=True)
def __str__(self):
return self.title_en
class Meta:
verbose_name = "Slider Items Module"
verbose_name_plural = "Slider Item Module"
Also if a show is selected and a movie isn't, how do i know title_en will be taken from show and not movie?
I think you can do something like this:
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
class Slider_items(models.Model):
order = models.IntegerField(max_length=3, blank=True)
# don't forget to add null=True to both fields
movie = models.ForeignKey('movies.movie', on_delete=models.CASCADE, blank=True, null=True)
show = models.ForeignKey('shows.show', on_delete=models.CASCADE, blank=True, null=True)
# see docs, https://docs.djangoproject.com/en/3.2/ref/models/instances/#django.db.models.Model.clean
def clean(self):
if self.movie and self.show:
raise ValidationError({'movie': _('You can't select both types at the same time')})
elif not self.movie and not self.show:
raise ValidationError({'movie': _('You must select one type')})
def __str__(self):
return self.movie.title_en if self.movie else self.show.title_en
class Meta:
verbose_name = "Slider Items Module"
verbose_name_plural = "Slider Item Module"
You may consider using django contenttypes.
Imagine in the future, you have not just Movie, Show, but have new Class such as Book, Podcase, it might not be a good idea to keep adding new foreignkey to your Slider Model.
I have not used contenttype before, so I am referencing this SO answer.
(using python 3.6, django 3.2)
models.py
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Movie(models.Model):
title = models.CharField(max_length=50)
director = models.CharField(max_length=50)
class Show(models.Model):
title = models.CharField(max_length=50)
date = models.DateField()
class Slider(models.Model):
order = models.IntegerField(max_length=3, blank=True)
choices = models.Q(model='movie') | models.Q(model='show')
selection_type = models.ForeignKey(
ContentType, limit_choices_to=choices,
on_delete=models.CASCADE)
selection_id = models.PositiveIntegerField()
selection = GenericForeignKey('selection_type', 'selection_id')
def __str__(self):
return self.selection.title
admin.py
#admin.register(Slider)
class SliderAdmin(admin.ModelAdmin):
pass
at django shell, the following is valid.
movie = Movie.objects.create(title='movie 1', director='ben')
show = Show.objects.create(title='show 1', date='2021-01-01')
s1 = Slider.objects.create(selection=movie, order=1)
s2 = Slider.objects.create(selection=show, order=2)
However, using limit_choices_to only restrict the choices in admin page, and there is no constraint at database level. i.e. the following are actually legal.
place = Place.objects.create(name='home')
s3 = Slider.objects.create(selection=s3, order=3)
I have not found a fix for this issue yet. Maybe doing some validation in save method is a way (see the comments under this).

how to make a form field exact?

In django how to make form field exact, i.e it will have choices?
My forms.py:
from django import forms
class FilterForm(forms.Form):
category = forms.CharField()
price = forms.IntegerField()
My models.py:
CATEGORY_CHOICES = (
('Fruits and Vegetables', 'Fruits and Vegetables'),
('Electronics', 'Electronics'),
('Clothing', 'Clothing'),
('Books', 'Books'),
)
class Item(models.Model):
title = models.CharField(max_length=120)
price = models.FloatField()
discount_price = models.FloatField(blank=True, null=True)
category = models.CharField(choices=CATEGORY_CHOICES, max_length=120, null=True, blank=True)
image_url = models.CharField(max_length=2083, null=True, blank=True)
slug = models.SlugField(null=True, blank=True)
description = models.TextField(null=True, blank=True)
Please make use of a ModelForm [Django-doc]. A ModelForm is capable of automating a lot of aspects when creating or update model records. Furthermore it can automatically construct the fields based on the fields of the model. You can, if you want to, alter the widgets, etc. But usually a ModeLField is a good starting point.
Here you thus can construct a form like:
# app/forms.py
from django import forms
from app.models import Item
class FilterForm(forms.ModelForm):
class Meta:
model = Item
fields = ['category', 'price']
Where you replace app with the name of the app.
You can use ModelForm
from django import forms
from .models import Item
class FilterForm(forms.ModelForm):
class Meta:
model = Item
fields = [
'category',
'price'
]
If you wanna stick with Form, use Choice Field and copy the Choices in form
from django import forms
class FilterForm(forms.Form):
CATEGORY_CHOICES = (
('Fruits and Vegetables', 'Fruits and Vegetables'),
('Electronics', 'Electronics'),
('Clothing', 'Clothing'),
('Books', 'Books'),
)
category = forms.ChoiceField(choices=CATEGORY_CHOICES)
price = forms.IntegerField()

Django 1.11 many to many does not appear in django admin

Hi i have a django model for notification which have a many-to-many relation but nothing appears in django admin ( all fields do not appear)
class Notification(models.Model):
"""send notification model"""
title = models.CharField(max_length=200)
text = models.TextField(null=True, blank=True)
device = models.ManyToManyField(Device, null=True, blank=True)
country = models.ManyToManyField(Country, null=True, blank=True)
sent = models.BooleanField(default=False)
when i open django admin for this model and press add notification this is what happens (nothing appears)
Country and Device Code
class Device(models.Model):
"""Store device related to :model:`accounts.User`."""
user = models.OneToOneField(User, related_name='device', on_delete=models.CASCADE)
model = models.CharField(max_length=200, blank=True, null=True)
player_id = models.CharField(max_length=200, blank=True, null=True)
class Meta:
verbose_name = 'Device'
verbose_name_plural = 'Devices'
def __str__(self):
return self.model
class Country(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
Admin.py
from django.contrib import admin
from .models import Notification
admin.site.register(Notification)
Edit:
Thank you all the problem is solved
The problem was caused by some entries in device model that did have None in the model field so there was a problem displaying it correctly.
According to https://code.djangoproject.com/ticket/2169 :
When a class has a field that isn't shown in the admin interface but
must not be blank, it's impossible to add a new one. You get a cryptic
"Please correct the error below." message with no shown errors. The
error message should probably say something about a hidden field.
Now ManyToManyFields don't need null=True, try removing those statements and see if you get an improvement.
Also, try adding the Country and Device models in admin.py so admin can see them and display them.
https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#working-with-many-to-many-models
Define an inline for the many-to-manys in admin.py:
from django.contrib import admin
class DeviceInline(admin.TabularInline):
model = Notification.device.through
class CountryInline(admin.TabularInline):
model = Notification.country.through
class NotificationAdmin(admin.ModelAdmin):
inlines = [
DeviceInline, CountryInline
]
exclude = ("device", "country")