Formatting inline many-to-many related models presented in django admin - python

I've got two django models (simplified):
class Product(models.Model):
name = models.TextField()
price = models.IntegerField()
class Invoice(models.Model):
company = models.TextField()
customer = models.TextField()
products = models.ManyToManyField(Product)
I would like to see the relevant products as a nice table (of product fields) in an Invoice page in admin and be able to link to the individual respective Product pages.
My first thought was using the admin's inline - but django used a select box widget per related Product. This isn't linked to the Product pages, and also as I have thousands of products, and each select box independently downloads all the product names, it quickly becomes unreasonably slow.
So I turned to using ModelAdmin.filter_horizontal as suggested here, which used a single instance of a different widget, where you have a list of all Products and another list of related Products and you can add\remove products in the later from the former. This solved the slowness, but it still doesn't show the relevant Product fields, and it ain't linkable.
So, what should I do? tweak views? override ModelForms? I Googled around and couldn't find any example of such code...

Maybe it is not what you expect but I would introduce InvoiceItem model which would link Invoice to Product. So you would have 2x 1:n instead of m:n relation. Then use inline custom form for InvoiceItem and raw_id_fields in that form for Product choice.
In InvoiceItem form then you could add readonly fields that would display values you need to display. You will have to provide data for these fields in Form's init reading them from InvoiceItem instance. Or you could also derive from raw_id_field widget and in render method of this widget append some additional data from the product model?

This is an old question, but I have related to it today.
You can find the answer here - https://blog.ionelmc.ro/2012/01/19/tweaks-for-making-django-admin-faster/
The code:
class MyAdmin(admin.TabularInline):
fields = 'myfield',
def formfield_for_dbfield(self, db_field, **kwargs):
formfield = super(MyAdmin, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name == 'myfield':
# dirty trick so queryset is evaluated and cached in .choices
formfield.choices = formfield.choices
return formfield
This can cut your waiting times from anything like 5 minutes to around 15 seconds.

Related

Is there a way to have multiple instances of a model embedded into a single model field in Django?

I know the foreign key concept,Djongo Array field , however is there an alternative?
The issue with the foreign key concept is that I would need to make multiple hits to the database and the issue with Array field is the lack of information and the errors emerging without a known solution.
What I would like to do basically is in fact add multiple instances of a model say comments to a single field in another model but I would like to embed it rather than creating a new table for comments which I tried using an abstract model however it did not work out.
I would like to know any alternative solution.
You can use foreign keys, and to avoid making separate query for every related record you can extract them all at once using prefetch_related - https://docs.djangoproject.com/en/3.1/ref/models/querysets/#prefetch-related :
Returns a QuerySet that will automatically retrieve, in a single batch, related objects for each of the specified lookups.
Code example:
# models.py
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
class Comment(models.Model):
post = models.ForeignKey(Post, models.CASCADE)
text = models.TextField()
# Somewhere else, fetch posts together with related comments:
# It will take 2 requests in total
# - one to fetch posts, another to fetch all related comments
for post in Post.objects.all().prefetch_related('comment_set'):
print([c.text for c in post.comment_set.all()])

Building a django detailview and listview without using <int:pk>

I'm building a django app that displays a database of employees and their salaries. It uses a postgres database. I'm struggling to figure out how to build a DetailView without using a primary key for the url. I think using a slug may be the solution but I'm not sure how to implement that and haven't had much luck searching online.
My Listview for the list of all companies:
class AllCompanies(FormMixin, generic.ListView):
template_name = 'AllCompanies.html'
form_class = SearchForm
model = bigdatabase
queryset = bigdatabase.objects.order_by().values('company').distinct()
context_object_name = 'companies'
Example Database snippet, bigdatabase:
Company EmployeeName Salary
Alpha Jim 100000
Alpha Tim 125000
Beta Bim 90000
My list view displays all unique company names as intended. However, I'm not sure how to proceed and build a detailview to display more info on each unique company. I'd like to show things like number of employees, median salary, etc.
I've done something similar by building a detailview for employees, but that relied upon using their primary key in the url since each employee is unique. Since I have many entries for many companies in my database, how would I build a detailview and accompanying url structure to support that?
Any advice or pointers as to move this along would be greatly appreciated.
You can add a slugfield to your bigdatabase model. Try using the autoslugfield and set it to the company name like:
from django_extensions.db.fields import AutoSlugField
class bigdatabase(models.Model):
company = Models.Charfield()
slug = models.AutoSlugField(populate_from=['company']
......
This makes sure you company name will automatically be translated for use in the url. E.g. when you have spaces in the company name, using str:company will translate in weird characters for the space. Using in your url translates this to a -.
Naming your model field slug makes sure that your detailview get the slug field by default. See docs.
Your views will then look something like:
class CompanyDetailView(DetailView):
model = bigdatabase
template_name = '#path_to_template'
slug_url_kwarg = 'slug' # I believe this is done correctly by default but in case you change the field name.
In your url you can do something like mentioned above:
path('company/<slug>', views.CompanyDetailView.as_view())
Happy coding!
Not sure that I understood correctly, but I thought you can use something like this:
path('company/<str:name>', views.company_detail_view)

Propper related objects creation within Django modelform

Suppose there are two models: Author and Book.
Sure enough Book has a foreign key to an Author.
There is a create view in which user gives a name of the author and uploads a file with the list of books it has.
So I am trying to figure out the best way to create a form.
Right now I have:
class AddForm(ModelForm):
books = FileField()
class Meta:
model = Author
def clean_books(self):
return [book.strip() for book in self.cleaned_data['books'].file]
The problem is where should I put the actual creation of Books model objects? Looks like it should be in a save method, something like:
def save(self, commit=True):
author = super().save(commit=True)
Book.objects.bulk_create([Book(author=author, title=book.title, ...) for book in self.cleaned_data['books']])
return author
But is it ok? What really annoys me is the commit argument. It's totally ignored and it may confuse others if they supply commit=False. How do I take into account commit argument and do not break the logic?
Take look at inline formsets. Using them you can add bunch of inline forms inside your main form. That formset will handle for you all processing of data and saving Book instances into database. It's like inline in django admin.

Splitting field in Django admin "Add" interface into its constituent fields

I am using Django 1.6. In the "Add" interface for one of my models, I have a field that is a foreign key to another model. Therefore it is displayed as a dropdown box containing the string representation of the second model. I want to be able to split it up into its constituent fields instead. Is there a way to do this?
ie. For example, in my "Add" screen for the model for "User", I have a field "Favourite Book". "Book" is displayed as a dropdown menu with string representations "Title, Author" for all books in the database, and I want to be able to display two dropdown menus instead, one for each of the fields Title and Author.
EDIT
This isn't my actual application. In my application, there is the added feature that all Author-Title combinations are possible (obviously this is not really the case for this example), so it would be very useful to be able to select the Title and Author separately rather than from a giant drop down menu containing all possible permutations.
What you are saying doesn't really make sense. The foreign key dropdown represents all Book objects in your database and allows you to create a relationship between your User and the particular Book that you select, i.e. that particular title/author combination. You can't select title and author independently as they are fields in a single Book and represent that particular Book (not to mention that title is a text field)
You could use a Django Admin Inline. You can see an example of inlines in this question:
This would allow you to relate numerous Books to a single User within the same admin page.
# models.py
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=100)
# admin.py
from django.contrib import admin
class BookInline(admin.StackedInline):
model = Book
class UserInline(admin.ModelAdmin):
inlines = [
BookInline,
]

displaylist of children in parent model one-to-many

I am learning django admin, i have to models with a one-to-many relation between them.
I got something like Manufacturer model where i can add different car manufacturers, and Car model for adding cars. In my django admin page i want to be able to display_list of all cars manfuctred by say manufacturer1 when i click on manufacturer1 entry.
I have found a trick by using Inline model in manufacturer admin model, the problem is that it loads every entry in the database and it takes some time as it's a big table.
Is there any method else to do that or do i have to create a new template ?
EDIT:
The goal is not to load every Car that is FK to Manufacturer1 like with InlineModelAdmin but to get the same display as with display_list with results split in pages
Answer for your updated question:
the way to do it could be by using ProxyModels and overriding ModelAdmin.queryset
You extend the Car model via FordCar, GMCar and use proxy=True in the Meta class, for both those.
Then you can register seperate admins for each of FordCar and GMCar and override the queryset method in each of those ModelAdmin to filter for the respective manufacturer.
Ex:
class FordCarAdmin(admin.ModelAdmin)
list_display = fields = ['name','model','engine']
def queryset(self,request):
qs = super(MyModelAdmin, self).queryset(request)
return qs.filter(manufacturer__name='Ford')
admin.site.register(FordCar,FordCarAdmin)
You have two options.
The easiest approach is to look at the relationship in reverse. Instead of going to a manufacturer change form and seeing all their cars. Go to the cars changelist and filter by manufacturer. You'll have to set the list_filter attribute on the car ModelAdmin to include manufacturer.
Option two, is going to be a huge pain, but you can override change_view on the manufacturer ModelAdmin to add the list of that manufacturer's cars to extra_context. Then, you'll have to override the admin template at 'templates/admin/yourapp/manufacturer/change_form.html'. You can then add to that template to produce the kind of list you're looking for using the list of cars you passed into extra_context, drawing on 'django/contrib/admin/templates/change_list.html' for inspiration.
Give the Django docs on the Admin a good thorough read. There's actually a wealth of info in there.
You don't need any hack. Django admin displays only Cars that have a FK to Manufacturer1, when you select Manufacturer1, as long as you have used the InlineModelAdmin right and as intended.

Categories

Resources