I've got marketplace models with fields:
class VacationEvent(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
process_fba = models.BooleanField(default=True)
#property
def has_amazon_fba_vacation(self):
return hasattr(self, 'amazon_fba_vacation')
And in admin.py:
class FBAInline(admin.StackedInline):
model = AmazonFBAVacation
can_delete = False
verbose_name_plural = 'amazon fba vacation'
fk_name = 'event'
I need to display conditionally FBAInline class when creating\updating VacationEventAdmin. list_filter shows true\false values of "process_fba" field, so if it is true - FBAInline should be displayed:
#admin.register(VacationEvent)
class VacationEventAdmin(admin.ModelAdmin):
inlines = []
list_display = ('id', 'user', 'holiday_name', 'start_date', 'end_date', 'process_fba', 'process_fbm', 'process_walmart', 'process_ebay')
list_filter = ['process_fba', 'process_fbm', 'process_walmart', 'process_ebay', 'status',]
search_fields = ['holiday_name', 'user__username']
# if VacationEvent.process_fba and VacationEvent.has_amazon_fba_vacation:
# inlines.append(FBAInline)
# try:
# getattr(VacationEvent, 'process_fba')
# except AttributeError:
# print "doesn't"
# else:
# inlines.append(FBAInline)
I tried getting attr but don't understant how to compare field values for admin. Thanks for any help.
Override get_formsets_with_inlines method
get_formsets_with_inlines(self, request, obj=None):
for inline in self.get_inline_instances(request, obj):
if obj and obj.has_amazon_fba_vacation:
yield inline.get_formset(request, obj), inline
Related
I have a following inline model under company management. i want to make editable those custom field under company detail page. anyone has a suggestion that how to make editable field 'email', 'name' and 'role'.
I will send it to API while submitting. so it's not concern that how to save it.
Following is my inline model
class CompanyUserInfoTAB(admin.TabularInline):
model = Userrolemapping
fields = ['id', 'email', 'name','role']
extra = 0
can_delete = False
verbose_name = 'COMPANY USERs'
verbose_name_plural = verbose_name
def email(self, obj):
return obj.user.email
def name(self, obj):
return obj.user.name
def role(self, obj):
return UserType.objects.get(usr_type_id=obj.role_id).name
def company_id(self, obj):
return obj.company.id
def get_queryset(self, request):
qs = super(CompanyUserInfoTAB, self).get_queryset(request)
return qs.exclude(mod_id="PSS")
Thanks In Advance.
I created a serializer which the "user" below is from another Serializer which i imported, now the imported serializer(PubliceProfileSerializer) works fine on its own but it does not display the content of USER when i call it my browser from this serializer. Every other item shows except the user. Please help
from rest_framework import serializers
from users.api.serializers import PublicProfileSerializer
from blog.models import Post
class PostSerializer(serializers.ModelSerializer):
user = PublicProfileSerializer(source='users.profile', read_only=True)
category = serializers.SerializerMethodField()
label = serializers.SerializerMethodField()
class Meta:
model = Post
fields = '__all__'
def get_category(self, obj):
return obj.get_category_display()
def get_label(self, obj):
return obj.get_label_display()
Add you your serializer the list of fields. Replace
fields = '__all__'
with
fields = ('id', 'user', 'category', 'label')
because:
fields = '__all__'
will only populate id, category and label from the Post model, but will not provide the nested user serializer, so it becomes:
class Meta:
model = Post
fields = ('id', 'user', 'category', 'label')
or
class PostSerializer(serializers.ModelSerializer):
user = PublicProfileSerializer(many=False,
source='users.profile',
read_only=True)
category = serializers.SerializerMethodField()
label = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ('id', 'user', 'category', 'label')
def get_category(self, obj):
return obj.get_category_display()
def get_label(self, obj):
return obj.get_label_display()
DjangoVersion:2.1.7
PythonVersion:3.8.2
I have a StoreLocation model which has ForeignKeys to Store, City and Site (this is the default DjangoSitesFramework model). I have created TabularInline for StoreLocation and added it to the Store admin. everything works fine and there isn't any problem with these basic parts.
now I'm trying to optimize my admin database queries, therefore I realized that StoreLocationInline will hit database 3 times for each StoreLocation inline data.
I'm using raw_id_fields, managed to override get_queryset and select_related or prefetch_related on StoreLocationInline, StoreAdmin and even in StoreLocationManager, i have tried BaseInlineFormsSet to create custom formset for StoreLocationInline. None of them worked.
Store Model:
class Store(models.AbstractBaseModel):
name = models.CharField(max_length=50)
description = models.TextField(blank=True)
def __str__(self):
return '[{}] {}'.format(self.id, self.name)
City Model:
class City(models.AbstractBaseModel, models.AbstractLatitudeLongitudeModel):
province = models.ForeignKey('locations.Province', on_delete=models.CASCADE, related_name='cities')
name = models.CharField(max_length=200)
has_shipping = models.BooleanField(default=False)
class Meta:
unique_together = ('province', 'name')
def __str__(self):
return '[{}] {}'.format(self.id, self.name)
StoreLocation model with manager:
class StoreLocationManager(models.Manager):
def get_queryset(self):
return super().get_queryset().select_related('store', 'city', 'site')
class StoreLocation(models.AbstractBaseModel):
store = models.ForeignKey('stores.Store', on_delete=models.CASCADE, related_name='locations')
city = models.ForeignKey('locations.City', on_delete=models.CASCADE, related_name='store_locations')
site = models.ForeignKey(models.Site, on_delete=models.CASCADE, related_name='store_locations')
has_shipping = models.BooleanField(default=False)
# i have tried without manager too
objects = StoreLocationManager()
class Meta:
unique_together = ('store', 'city', 'site')
def __str__(self):
return '[{}] {} / {} / {}'.format(self.id, self.store.name, self.city.name, self.site.name)
# removing shipping_status property does not affect anything for this problem.
#property
def shipping_status(self):
return self.has_shipping and self.city.has_shipping
StoreLocationInline with custom FormSet:
class StoreLocationFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.queryset = self.queryset.select_related('site', 'city', 'store')
class StoreLocationInline(admin.TabularInline):
model = StoreLocation
# i have tried without formset and multiple combinations too
formset = StoreLocationFormset
fk_name = 'store'
extra = 1
raw_id_fields = ('city', 'site')
fields = ('is_active', 'has_shipping', 'site', 'city')
# i have tried without get_queryset and multiple combinations too
def get_queryset(self, request):
return super().get_queryset(request).select_related('site', 'city', 'store')
StoreAdmin:
class StoreAdmin(AbstractBaseAdmin):
list_display = ('id', 'name') + AbstractBaseAdmin.default_list_display
search_fields = ('id', 'name')
inlines = (StoreLocationInline,)
# i have tried without get_queryset and multiple combinations too
def get_queryset(self, request):
return super().get_queryset(request).prefetch_related('locations', 'locations__city', 'locations__site')
so when i check Store admin change form, it will do this for each StoreLocationInline item:
DEBUG 2020-04-13 09:32:23,201 [utils:109] (0.000) SELECT `locations_city`.`id`, `locations_city`.`is_active`, `locations_city`.`priority`, `locations_city`.`created_at`, `locations_city`.`updated_at`, `locations_city`.`latitude`, `locations_city`.`longitude`, `locations_city`.`province_id`, `locations_city`.`name`, `locations_city`.`has_shipping`, `locations_city`.`is_default` FROM `locations_city` WHERE `locations_city`.`id` = 110; args=(110,)
DEBUG 2020-04-13 09:32:23,210 [utils:109] (0.000) SELECT `django_site`.`id`, `django_site`.`domain`, `django_site`.`name` FROM `django_site` WHERE `django_site`.`id` = 4; args=(4,)
DEBUG 2020-04-13 09:32:23,240 [utils:109] (0.000) SELECT `stores_store`.`id`, `stores_store`.`is_active`, `stores_store`.`priority`, `stores_store`.`created_at`, `stores_store`.`updated_at`, `stores_store`.`name`, `stores_store`.`description` FROM `stores_store` WHERE `stores_store`.`id` = 22; args=(22,)
after i have added store to the select_related, the last query for store disappeared. so now i have 2 queries for each StoreLocation.
I have checked the following questions too:
Django Inline for ManyToMany generate duplicate queries
Django admin inline: select_related
Problem is caused by ForeignKeyRawIdWidget.label_and_url_for_value()
as #AndreyKhoronko mentioned in comments, this problem is caused by ForeignKeyRawIdWidget.label_and_url_for_value() located in django.contrib.admin.widgets which will hit database with that key to generate a link to admin change form.
class ForeignKeyRawIdWidget(forms.TextInput):
# ...
def label_and_url_for_value(self, value):
key = self.rel.get_related_field().name
try:
obj = self.rel.model._default_manager.using(self.db).get(**{key: value})
except (ValueError, self.rel.model.DoesNotExist, ValidationError):
return '', ''
try:
url = reverse(
'%s:%s_%s_change' % (
self.admin_site.name,
obj._meta.app_label,
obj._meta.object_name.lower(),
),
args=(obj.pk,)
)
except NoReverseMatch:
url = '' # Admin not registered for target model.
return Truncator(obj).words(14, truncate='...'), url
I have a ModelForm:
class SomeModelForm(forms.ModelForm):
class Meta:
model = SomeModel
def __init__(self, *args, **kwargs):
super(ApiBackendConfigForm, self).__init__(*args, **kwargs)
if kwargs['instance'].name == u'Some_name':
self.fields['method_config'] = forms.URLField()
and ModelAdmin:
class SomeAdmin(admin.ModelAdmin):
form = SomeModelForm
list_display = ('name', 'alias', 'is_enabled', )
list_editable = ('is_enabled', )
readonly_fields = ('name', 'alias', )
First question, method_config field is not displayed. I know, that it's not in list_display, but if I add it to list_display, then it causes an error.
And second main question: How can I add some link to other ModelAdmin?
Modifing self.fields might not be thread-safe. This means that if you modify self.fields on first request, all other requests will get that modified version.
Check ModelAdmin.get_fields(request, obj=None) method for changing which fields to display on the fly. Works on forms in changeview. If you want to display custom field in changelist view, just modify list_display wih method name instead of field.
For example:
class SomeAdmin(admin.ModelAdmin):
list_display = ('name', 'alias', 'is_enabled', 'show_method_config', )
list_editable = ('is_enabled', )
readonly_fields = ('name', 'alias', )
def show_method_config(self,obj):
return getattr(obj,"method_config","") if obj.name == u"Some_name" else ""
show_method_config.short_description = _(u"Method config")
show_method_config.admin_order_field = "method_config"
show_method_config.allow_tags = True
def get_fields(self, request, obj=None):
fields = super(SomeAdmin, self).get_fields(request, obj)
if obj and obj.name == u"Some_name":
fields.append("method_config")
return fields
I have two tables related Quiz and Difficulty_level:
I have created inline in admin.py like this:
class DifficultyLevelInline(admin.TabularInline):
model = DifficultyLevel
and included in QuizAdmin
To arrange the list order, I would do:
list_display = ('name', 'description', 'publication_date', 'category', 'is_active', 'is_premium')
How can I add inlines in the list_display order. I want to display The DifficultyLevelInline before category.
Unfortunately this is not possible using the default template.
If you take a look at change_form template:
https://github.com/django/django/blob/master/django/contrib/admin/templates/admin/change_form.html
You can see that inlines are always rendered after fieldsets.
One way to get around this would be to use other template:
class MyAdmin(admin.ModelAdmin):
list_display = ('name', 'description', 'publication_date', 'category', 'is_active', 'is_premium')
inlines = (DifficultyLevelInline,)
change_form_template = "my_change_form.html"
Grapelli supports it: https://django-grappelli.readthedocs.org/en/latest/customization.html#rearrange-inlines
Basically, it uses a placeholder via fieldsets and then moves them HTML via JavaScript:
https://github.com/sehmaschine/django-grappelli/blob/master/grappelli/templates/admin/change_form.html#L90-96 (search for placeholder, if the lines do not match anymore).
The same can be done by injecting custom javascript yourself (with or without using fieldsets as placeholders).
Suppose here is your inline model:
# models.py
from django.contrib.auth.models import Group
class MoreGroup(models.Model):
group = models.OneToOneField(Group, on_delete=models.CASCADE, related_name='more_group')
explain = models.CharField(verbose_name="explain_info", max_length=64, blank=True, null=True)
active = models.BooleanField(verbose_name="is_actived", default=True, blank=True, null=True)
then do this:
# admin.py
from . import models
class MoreGroupInline(admin.StackedInline):
model = models.MoreGroup
can_delete = False
verbose_name_plural = 'more_info'
class MyGroupAdmin(GroupAdmin):
list_display = ['id', 'name', 'get_inline_info']
def get_inline_info(self, obj) -> str:
mg = models.MoreGroup.objects.filter(group=obj)
if mg.count():
return mg[0].explain
else:
return '-'
get_inline_info.short_description = 'explain_info'
admin.site.register(models.Group, MyGroupAdmin)
I found a solution here: https://blog.devgenius.io/django-admin-dynamic-inline-positioning-7208596479ce
class MyAdmin(admin.ModelAdmin):
list_display = ('name', 'description', 'difficulty_level_inline', 'publication_date', 'category', 'is_active', 'is_premium')
inlines = (DifficultyLevelInline,)
def difficulty_level_inline(self, *args, **kwargs):
context = getattr(self.response, 'context_data', None) or {}
inline = context['inline_admin_formset'] = context['inline_admin_formsets'].pop(0)
return get_template(inline.opts.template).render(context, self.request)
def render_change_form(self, request, *args, **kwargs):
self.request = request
self.response = super().render_change_form(request, *args, **kwargs)
return self.response