How to edit custom field in django TabularInline - django

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.

Related

How can I sort by calculated fields in Django Admin?

I have a calculated method in my model, and in admin page want to add sort functionality for that calculated field.
For now, I am using aggregation for model calculated fields and displaying that in admin.
How can I add sort function for this field in admin page?
class CoinWallet(Account):
student = models.OneToOneField(
'students.Student',
on_delete=models.PROTECT,
related_name="coinWallet",
null=True)
#admin.display(description='Total Coins')
def total_coins(self):
positive_movements_aggregate = self.movement_set.filter(
side=self.positive_side,
).aggregate(models.Sum('amount'))
positive_movements_balance = positive_movements_aggregate[
'amount__sum'] if positive_movements_aggregate['amount__sum'] else 0
return f'{positive_movements_balance}'
#admin.register(CoinWallet)
class CoinWalletAdmin(admin.ModelAdmin):
list_display = ('id', 'student', 'balance', 'total_coins', 'answer_coins', 'bonus_coins', )
search_fields = ('id', 'student')
def get_queryset(self, request):
queryset = super(CoinWalletAdmin, self).get_queryset(request)
queryset = queryset.annotate(_total_coins=ExpressionWrapper(F('balance'), output_field=DecimalField())).order_by('_total_coins')
return queryset
def total_coins(self, obj):
return obj._total_coins
total_coins.admin_order_field = '_total_coins'
I tried this, but, instead of balance, i want to add sortable for total_coins.
What should I do in this admin class?
Use order_by instead:
class CoinWalletAdmin(admin.ModelAdmin):
...
def get_queryset(self, request):
qs = super(CoinWalletAdmin, self).get_queryset(request)
qs = qs.order_by(F('total_coins')
return qs

Django Admin Inline hit database multiple times

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

Django Rest Framework SerializerMethodField only on GET Request

Running into a little snag here with my DRF backend.
I am populating fields with choices on certain models.
I have a foreign key requirement on one model. When I create the model I want to save it under the foreign id.
When I request the models, I want the model with whatever the choice field maps to.
I was able to do this with SerializerMethodField, however when I try to create a model, I get a 400 error because the block is not valid. If I remove the SerializerMethodField, I can save, but get the number stored in the db from the request.
Any help would be appreciated.
class BlockViewSet(ModelViewSet):
model = apps.get_model('backend', 'Block')
queryset = model.objects.all()
serializer_class = serializers.BlockSerializer
permissions = ('All',)
def create(self, request, format=None):
data = request.data
data['user'] = request.user.id
data['goal'] = WorkoutGoal.objects.get(goal=data['goal']).id
block = serializers.BlockSerializer(data=data, context={'request': request})
if block.is_valid():
new_block = block.save()
return Response({'block': {'name': new_block.name, 'id': new_block.id}}, status=status.HTTP_201_CREATED)
else:
return Response(block.errors, status=status.HTTP_400_BAD_REQUEST)
class WorkoutGoalSerializer(serializers.ModelSerializer):
class Meta:
model = apps.get_model('backend', 'WorkoutGoal')
fields = ('goal',)
goal = serializers.SerializerMethodField(read_only=True, source='get_goal')
def get_goal(self, obj):
return dict(WorkoutGoal.GOALS).get(obj.goal)
class BlockSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
goal = WorkoutGoalSerializer()
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
The above code returns the correct choice, but I can't save under it. Remove the goal = WorkoutGoalSerializer() and it saves but doesn't return the mapped choice.
I think this will work like a charm,
class WorkoutGoalSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'request' in self.context and self.context['request'].method == 'GET':
self.fields['goal'] = serializers.SerializerMethodField(read_only=True, source='get_goal')
class Meta:
model = apps.get_model('backend', 'WorkoutGoal')
fields = ('goal',)
goal = serializers.SerializerMethodField(read_only=True, source='get_goal') # remove this line
def get_goal(self, obj):
return dict(WorkoutGoal.GOALS).get(obj.goal)
How this Work?
It will re-initiate the goal field with SerializerMethodField, if the reuested method is GET.
Remember one thing, you should remove the line,
goal = serializers.SerializerMethodField(read_only=True, source='get_goal')
serializers.py
class BlockCreateSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
class BlockSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
goal = WorkoutGoalSerializer()
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
views.py
class BlockViewSet(ModelViewSet):
model = apps.get_model('backend', 'Block')
queryset = model.objects.all()
serializer_class = serializers.BlockSerializer
permissions = ('All',)
def get_serializer_class(self):
if self.action == 'create':
return serializers.BlockCreateSerializer
else:
return self.serializer_class
def create(self, request, format=None):
data = request.data
data['user'] = request.user.id
data['goal'] = WorkoutGoal.objects.get(goal=data['goal']).id
block = self.get_serializer(data=data)
if block.is_valid():
new_block = block.save()
return Response({'block': {'name': new_block.name, 'id': new_block.id}}, status=status.HTTP_201_CREATED)
else:
return Response(block.errors, status=status.HTTP_400_BAD_REQUEST)
override get_serializer_class to return different serializer_class for create and other action(list\retrieve\update\partial_update)

Django Admin conditional display inlines based on models property

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

Add link to ModelAdmin

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