Significant performance issue with Django Admin - foreign key labels - python

I’m experience significant performance issue with Django Admin.
I have a mapping model where I map primary keys of 2 other modes
In my FundManagerMappingAdmin I try to represent the foreign key of the 2 tables with a label from the foreign key models.
The underlying models are about 4000 lines
I’m experiencing slow performance when retrieving the list in admin and then also when editing and updating
Could someone please point out the inefficiencies in this code?
Is there a better way please?
Admin.py
#admin.register(ChampDwDimFundManagerMapping)
class FundManagerMappingAdmin(admin.ModelAdmin):
list_display = ['get_champ_fund_manager_id', 'get_evestment_name', 'get_sharepoint_name', ]
def get_champ_fund_manager_id(self, obj):
return obj.fund_manager_id
get_champ_fund_manager_id.short_description = 'CHAMP Manager ID'
def get_evestment_name(self, obj):
return obj.evestment_fund_manager_id.manager_name
get_evestment_name.short_description = 'Evestment Manager Name'
def get_sharepoint_name(self, obj):
return obj.sharepoint_fund_manager_id.manager_name
get_sharepoint_name.short_description = 'Sharepoint Manager Name'
def get_form(self, request, obj=None, **kwargs):
form = super(ChampFundManagerMappingAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['sharepoint_fund_manager_id'].label_from_instance = lambda obj: "{} {}".format(obj.final_publications_fund_manager_id, obj.manager_name)
form.base_fields['evestment_fund_manager_id'].label_from_instance = lambda obj: "{} {}".format(obj.evestment_fundmanager_id_bk, obj.manager_name)
return form
Models.py
class FundManagerMapping(models.Model):
fund_manager_id = models.AutoField(db_column='FundManagerId', primary_key=True)
sharepoint_fund_manager_id = models.ForeignKey(SharePointFundManager, models.DO_NOTHING, db_column='SharePointFundManagerId')
evestment_fund_manager_id = models.ForeignKey(EvestmentFundManager, models.DO_NOTHING, db_column='eVestmentFundManagerId')
class EvestmentFundManager(models.Model):
evestment_fund_manager_id = models.AutoField(db_column='eVestmentFundManagerId', primary_key=True)
package_execution_id = models.IntegerField(db_column='PackageExecutionId')
evestment_fund_manager_id_bk = models.CharField(db_column='eVestmentFundManagerId_BK', max_length=50)
manager_name = models.CharField(db_column='ManagerName', max_length=255)
class SharePointFundManager(models.Model):
sharepoint_fund_manager_id = models.AutoField(db_column='SharePointFundManagerId', primary_key=True)
package_execution_id = models.IntegerField(db_column='PackageExecutionId')
research_fund_manager_id = models.CharField(db_column='ResearchFundManagerId', max_length=50, blank=True, null=True)
final_publications_fund_manager_id = models.CharField(db_column='FinalPublicationsFundManagerId', max_length=50, blank=True, null=True)
manager_name = models.CharField(db_column='ManagerName', max_length=255)

You are showing the name of related entities (because of get_evestment_name and get_sharepoint_name) without joining/prefetching them. That means for every row that you display and every name of the related entity it requires django to make a database query. You need to override get_queryset() of the ModelAdmin and use select_related to tell django to join those entities from the beginning so that it does not need any additional queries to get those names:
#admin.register(ChampDwDimFundManagerMapping)
class FundManagerMappingAdmin(admin.ModelAdmin):
def get_queryset(self, request):
return super().get_queryset(request).select_related(
'sharepoint_fund_manager_id',
'evestment_fund_manager_id',
)
Also you don't name ForeignKey fields something_id. It is just sharepoint_fund_manager because what you get when you call fund_manager.sharepoint_fund_manager_id is not an id but an instance of SharePointFundManager. It is weird to call sharepoint_fund_manager_id.name. An id does not have a name attribute. A fund manager has.
Additionally Django does automatically create a property sharepoint_fund_manager_id for you if you call the field sharepoint_fund_manager to access the plain foreign key.

Related

save() saves all fields except ManyToMany field

I have a model "Contest" with one m2m field called "teams" which is related to a model "Team".
I overrided the method save. In my save() function (the one that's overriding), I need a queryset (in my save overrinding function) with all the objects related to my team m2m field. The code is self.teams.all() but it won't work because my models is not yet registered in database right ? So I call super().save(*args, **kwargs) now my models is saved and I can get my queryset ?
I can't. The queryset is empty, even if I registered team(s). <QuerySet []>
Why does super.save() save immediately all the fields except the m2m ?
I use exclusively the django admin web site to create models. No manage.py shell or form.
My model :
class Contest(models.Model):
name = models.CharField(max_length=16, primary_key=True, unique=True, default="InactiveContest", blank=True) # Ex: PSGvMNC_09/2017
id = models.IntegerField(default=1)
teams = models.ManyToManyField(Team, verbose_name="opposants")
date = models.DateTimeField(blank=True)
winner = models.ForeignKey(Team, verbose_name='gagnant', related_name='winner', on_delete=models.SET_NULL, blank=True, null=True)
loser = models.ForeignKey(Team, verbose_name='perdant', related_name='loser', on_delete=models.SET_NULL, blank=True, null=True)
bet = models.IntegerField(verbose_name='Nombre de paris', default=0, blank=True, null=0)
active = models.BooleanField(default=False)
def save(self, *args, **kwargs):
if self._state.adding:
self.active = False
# Django's id field immitation
last_id = Contest.objects.all().aggregate(Max('id')).get('id__max')
if last_id is not None:
self.id = last_id + 1
super().save(*args, **kwargs)
print(Contest.objects.get(id=self.id)) # Works --> model saved in db... in theory
queryset = self.teams.all() # Empty all the time !
Once save() (the overrinding one) has been executed one time, the problem is solved and next times (modifications) I can get my queryset so I could just use self._state.adding but this method obliges me to save 2 times (creation, editing).
I need to understand why super().save(*args, **kwargs)behaves like this and how can I solve this ?
The reason is that implementation of ManyToManyField does not use the database table of the model. It uses third table for connection between two models.
Each model has its own database table. In your example
model Contest -> table app_contest
model Team -> table app_team
However, app_contest does not have field teams in it. And app_team does not contain field contest.
Instead, there is a third table e.g. called app_contest_team that consists of three columns:
id
contest_id
team_id
And stores pairs of contest_id + team_id that represent connection between contests and teams.
So queryset = self.teams.all() is equal to the SQL expression:
SELECT * FROM app_teams WHERE id in
(SELECT team_id from app_contest_teams WHERE contest_id = '{self.id}');
Thus, you can see that you cannot anyhow manipulate ManyToManyField without having an instance initially saved in database. Because only after inserting into table app_contest it receives unique ID which can be later used for creating entries in the third table app_contest_teams.
And also because of that - save() is not required when you add new object to ManyToManyField because table of the model is not affected

Django model reference and manipulation

I have the following models in Django that have a structure as follows:
class Office_Accounts(models.Model):
accountid = models.EmailField(max_length=200, unique=True)
validtill = models.DateField(default=datetime.now)
limit = models.CharField(max_length=2)
class Device(models.Model):
device_type = models.ForeignKey(DeviceType,to_field='device_type')
serial_number = models.CharField(max_length=200,unique=True)
in_use_by = models.ForeignKey(User,to_field='username')
brand = models.CharField(max_length=200,default="-", null=False)
model = models.CharField(max_length=200,default="-", null=False)
type_number = models.CharField(max_length=200,blank=True,null=True, default = None)
mac_address = models.CharField(max_length=200,blank=True,null=True, default = None)
invoice = models.FileField(upload_to='Device_Invoice', null=True, blank = True)
msofficeaccount = models.ForeignKey(Office_Accounts, to_field="accountid")
class Meta:
verbose_name_plural = "Devices"
def full_name(self):
return self.device_type + self.serial_number + self.brand
I will display both of the models in admin.py.
Now, I want to display the count of each accountid present in the field "msofficeaccount" (present in Device Models) in my admin page of Office_Accounts model. For an example if xyz#abc.com appears in 10 rows of msofficeaccount field then, the count should be displayed as 10 in Office_Accounts admin page. Can anyone please guide me how should I approach this problem to solve it?
You could add a method to your admin class that returns the count of related devices for each office_account, but that would be very inefficient. Instead you can override get_queryset to annotate the count from a database aggregation function:
from django.db.models import Count
class Office_AccountsAdmin(admin.ModelAdmin):
list_display = (..., 'device_count')
...
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.annotate(device_count=Count('device'))
(On a minor note, Python style is always to use CamelCase for class names, and Django style is to use singular model names, so your model should really be called OfficeAccount.)

Django Rest Framework: Saving ForeignKey inside OneToOne model

I have 2 models that are OneToOne related and model that is FK to 2nd model
models.py
class Legal(TimeStampedModel):
name = models.CharField('Name', max_length=255, blank=True)
class LegalCard(TimeStampedModel):
legal = models.OneToOneField('Legal', related_name='legal_card', on_delete=models.CASCADE)
branch = models.ForeignKey('Branch', related_name='branch', null=True)
post_address = models.CharField('Post address', max_length=255, blank=True)
class Branch(TimeStampedModel):
name = models.CharField('Name',max_length=511)
code = models.CharField('Code', max_length=6)
Using DRF I made them to behave as single model so I can create or update both:
serializer.py
class LegalSerializer(serializers.ModelSerializer):
branch = serializers.IntegerField(source='legal_card.branch', allow_null=True, required=False)
post_address = serializers.CharField(source='legal_card.post_address', allow_blank=True, required=False)
class Meta:
model = Legal
fields = ('id',
'name',
'branch',
'post_address',
)
depth = 2
def create(self, validated_data):
legal_card_data = validated_data.pop('legal_card', None)
legal = super(LegalSerializer, self).create(validated_data)
self.update_or_create_legal_card(legal, legal_card_data)
return legal
def update(self, instance, validated_data):
legal_card_data = validated_data.pop('legal_card', None)
self.update_or_create_legal_card(instance, legal_card_data)
return super(LegalSerializer, self).update(instance, validated_data)
def update_or_create_legal_card(self, legal, legal_card_data):
LegalCard.objects.update_or_create(legal=legal, defaults=legal_card_data)
views.py
class LegalDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Legal.objects.all()
serializer_class = LegalSerializer
I'm trying to save this by sending FK as integer (I just want to post id of the branch), but I receive error
ValueError: Cannot assign "2": "LegalCard.branch" must be a "Branch" instance.
Is there any way to pass over only ID of the branch?
Thank you
In Django, if you only need the FK value, you can use the FK value that is already on the object you've got rather than getting the related object.
Assume you have a Legal and Branch object with id's as 1. Then you can save a LegalCard object by:
LegalCard(legal_id=1,branch_id=1,post_address="Istanbul Street No:1")
Just use legal_card.branch_id instead of legal_card.branch to get just an id, not a related object.
And depth = 1

Django Rest Framework: dynamic database on POST - RelatedField or PrimaryKeyRelatedField

I'm developing RESTFul services with DRF and I have multiple databases depending on the country (see my last question here)
I'm having a problem now with relationships, I have two models: Category and SubCategory:
class SubCategory(models.Model):
objects = CountryQuerySet.as_manager()
id = models.AutoField(primary_key=True,db_column='sub_category_id')
name = models.TextField()
female_items_in_category = models.BooleanField()
male_items_in_category = models.BooleanField()
kids_items_in_category = models.BooleanField()
category = models.ForeignKey('Category')
class Meta:
managed = True
db_table = Constants().SUBCATEGORY
And the serializer is:
class SubCategorySerializer(serializers.ModelSerializer):
category = PrimaryKeyRelatedField(queryset=Category.objects.using('es').all())
class Meta:
model = SubCategory
fields = ('id', 'name','female_items_in_category','male_items_in_category','kids_items_in_category','category')
If I don't set the queryset with the proper country it fails, because it doesn't know where to get the category.
Here the problem
I already set the country in the serializer context (in the ModelViewSet):
def get_serializer_context(self):
return {Constants().COUNTRY: self.kwargs.get(Constants().COUNTRY)}
But I can not find the proper way to get the self.context.get(Constants().COUNTRY) in the serializer.
Do you any have an idea to solve this? Thanks!
Well, I found a solution to my problem: I overwrite the method get_fields in the serializer:
def get_fields(self, *args, **kwargs):
fields = super(SubCategorySerializer, self).get_fields()
country = self.context.get(Constants().COUNTRY)
qs = Category.objects.using(country).all()
fields['category'].queryset = qs
return fields
And that works!

Django retrieve all related attributes for model

I am writing a store based app. Say theres Store A , Store A can have multiple users, and each user can belong to multiple stores. However, each store has Products and each Product has sizes associated with them.
class Variation(models.Model):
product = models.ForeignKey("Product")
size = models.CharField(choices=SIZES, max_length=5)
pid = models.CharField(unique=True, max_length=100, verbose_name="Product ID")
id_type = models.CharField(max_length=5, choices=ID_TYPES, default="UPC")
price = models.DecimalField(max_digits=5, decimal_places=2)
store = models.ForeignKey("Store")
class Meta:
unique_together = ("store", "pid")
class Product(models.Model):
item = models.CharField(max_length=100, verbose_name="Parent SKU", help_text="reference# - Color Code")
# Google docs sp key
store = models.ForeignKey(Store)
class Store(models.Model):
name = models.CharField(max_length=100)
store_id = models.PositiveIntegerField(unique=True)
admins = models.ManyToManyField(User)
objects = StoreManager()
def __unicode__(self):
return self.name
so i had to write a custom manager for Product in order to filter all products by store, and override the queryset method for this model's admin class, and do so for EVERY attribute belonging to said store. So basically my question is, is there a way to filter all attributes related to a store per store, ex products, tickets, variations
EDIT
This is the product manager so far
class ProductManager(models.Manager):
def active(self, **kwargs):
return self.filter(in_queue=False, **kwargs)
def by_store(self, store=None, **kwargs):
return self.filter(store__id__exact=store, **kwargs)
def from_user(self, request):
qs = self.model.objects.none()
for store in request.user.store_set.all():
qs = qs | store.product_set.filter(in_queue=False)
return qs
so basically, in order to display the products in the change list page, i use the from user method, which returns all the products available to the logged in user
it sounds like what you want to be using is the the django "Sites" framework
add a ForeignKey to Store to point to Site and make it unique.
it may be wise to point Variation and Products' ForeignKeys at Site instead of Store at this point too so that in your views you can filter your results by current site.

Categories

Resources