Display ForeignKey filed as a table in django Admin - python

I'm working on a project using Python(3.7) and Django(2.1) in which I have a model call Category which has a filed as parent of type ForeignKey to itself as self. It's displaying in the Django admin as a drop down, as there will be hundreds of PARENT categories, so we can't use a dropdown in such a situation, what I want to display a big table with a search option to select a category as parent.
Here's my Category model:
From models.py:
class Category(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField()
description = models.TextField(max_length=1000, default='')
parent = models.ForeignKey('self',
blank=True,
null=True,
related_name='children',
on_delete=models.CASCADE)
class Meta:
unique_together = ('slug', 'parent',)
verbose_name_plural = "categories"
def __str__(self):
full_path = [self.name]
k = self.parent
while k is not None:
full_path.append(k.name)
k = k.parent
return ' -> '.join(full_path[::-1])
And From admin.py:
#admin.register(Category)
class CatAdmin(admin.ModelAdmin):
filter = ('parent',)
class Media:
css = {
'all': ('assets/css/custom.css',),
}
so, how can I achieve my required scenario in Django admin?
Thanks in advance!

Related

Django Admin One-To-Many-To-One (Site->Product->Price) Relationship

I'm developing a web app for fun and the goal is to use Django Admin and only Django Admin, without any custom templates (yet). I'm unable to figure out how I should structure my models.py and admin.py files, where what I'm trying to do is:
There are many items and many departments: Each item can only belong to one department and each department can have several items. This currently works.
Now, what I can't seem to figure out is:
There are many sites. Each site can have many items, but the PRICE of each item at each site can be different. For example:
Site #123 can have a can of coke for $1.00
Site #124 can also have a can of coke, but at a price of $0.95
Site #123 can have a bag of chips for $1.50
Site #124 can also have a bag of chips, but at a price of $1.95
etc...
How do I establish this relationship in Django models.py/admin.py? Also, how could I edit the price using the inline (screenshot below)? In other words, how could the price be shown to the right of the description?
Thanks in advance
Current Code:
admin.py:
from django.contrib import admin
from .models import Site, Department, Item
class ItemInline(admin.TabularInline):
model = Site.items.through
can_delete = False
verbose_name = 'Item'
verbose_name_plural = 'Items'
extra = 0
#admin.register(Site)
class SiteAdmin(admin.ModelAdmin):
fields = ("site", "address")
list_display = ("site", "address")
inlines = (ItemInline, )
exclude = ("items", )
#admin.register(Department)
class DepartmentAdmin(admin.ModelAdmin):
fields = ("number", "description")
list_display = ("number", "description")
#admin.register(Item)
class ItemAdmin(admin.ModelAdmin):
fields = ("upc", "description", "department")
list_display = ("upc", "description", "department")
save_as = True
def formfield_for_dbfield(self, *args, **kwargs):
formfield = super().formfield_for_dbfield(*args, **kwargs)
formfield.widget.can_delete_related = False
formfield.widget.can_change_related = False
formfield.widget.can_add_related = False
# formfield.widget.can_view_related = False
return formfield
models.py:
from django.db import models
class Department(models.Model):
number = models.IntegerField(unique=True)
description = models.CharField(max_length=30)
def __str__(self):
return str(self.number) + ": " + self.description
class Meta:
ordering = ["number", ]
class Item(models.Model):
upc = models.CharField(max_length=30, unique=True, verbose_name="UPC")
description = models.CharField(max_length=30)
department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.upc + ": " + self.description
class Meta:
ordering = ["upc", ]
class Site(models.Model):
site = models.IntegerField(unique=True)
address = models.CharField(max_length=50)
items = models.ManyToManyField(Item)
def __str__(self):
return str(self.site)
class Meta:
ordering = ["site", ]
I figured this out about 20 minutes after posting. The key was to create a new model (named ItemPrice) with site, upc, and price -- with site and upc FK'd to Site and Item. I then created a new Inline pointing to ItemPrice:
class ItemPrice(models.Model):
site = models.ForeignKey(Site, on_delete=models.CASCADE, null=True)
upc = models.ForeignKey(Item, on_delete=models.CASCADE, null=True, verbose_name="UPC")
price = models.DecimalField(max_digits=6, decimal_places=2, default=0)
class Meta:
constraints = [
models.UniqueConstraint(fields=['site', 'upc'], name='unique_site_upc')
]
and
class ItemPriceInline(admin.TabularInline):
model = ItemPrice
# can_delete = False
verbose_name = 'Item Price'
verbose_name_plural = 'Item Prices'
extra = 0

Object has no a 'get_category_display'

I want to display product detail page using drf but I keep running into one error after another.
urls.py
path('product/<int:id>', views.product_detail_view.as_view(), name='product-detail'),
models.py
class Product(models.Model):
categories = models.ManyToManyField(Category)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="product_owner")
item = models.CharField(max_length=150)
slug = models.SlugField(max_length=255, blank=True, null=True)
brand = models.CharField(max_length=255, default="brand")
image = models.ImageField(upload_to="images/products/")
label = models.CharField(max_length=254, default='', blank=True, null=True)
serializers.py
class product_detail_serializer(serializers.ModelSerializer):
category = serializers.SerializerMethodField()
class Meta:
model = Product
fields = ("id", "categories", "item", "slug", "image")
lookup_field = "id"
def get_category(self, obj):
return obj.get_category_display()
views.py
class product_detail_view(generics.RetrieveAPIView):
serializer_class = product_detail_serializer
lookup_field = "id"
The error I'm getting now is 'Product has no attribute 'get_category_display'
Please how do I fix this error?
You can try defining the id inside the product_detail_serializer class then include it in the url patterns path like this:
serializers.py
class product_detail_serializer(serializers.ModelSerializer):
category = serializers.SerializerMethodField()
id = serializers.IntegerField(read_only=True)
Then include the id in the urlpatherns path
path('product/<int:id>',views.product_detail_view.as_view(),name='product
detail'),
I basically can't explain why this worked. I used the serializer for product instead of creating another serializer for the product detail page.

Django junction table with extra foreignkey

I have the following models
class Company(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(
max_length=500,
blank=True,
help_text='Any text to describe a company'
)
url = models.URLField('company URL', blank=True, null=True)
email = models.EmailField(blank=True, null=True)
created_on = models.DateTimeField('date created', default=timezone.now)
class Meta:
verbose_name = 'company'
verbose_name_plural = 'companies'
ordering = ['name', '-created_on']
def __repr__(self):
return '<Company {0.name}>'.format(self)
def __str__(self):
return self.name
class Project(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(
max_length=500,
blank=True,
help_text='Any text to describe the project'
)
company = models.ForeignKey(
Company,
on_delete=models.PROTECT,
)
created_on = models.DateTimeField('date created', default=timezone.now)
class Meta:
verbose_name = 'project'
verbose_name_plural = 'projects'
ordering = ['-created_on', 'company']
permissions = (
("can_view_project",
"Can view all project related work"),
)
def __repr__(self):
return '<Project {0.name}>'.format(self)
def __str__(self):
return self.name
class Worker(models.Model):
description = models.TextField(
max_length=500,
blank=True,
help_text='Optional. Describe what the worker does or who they are'
)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
company = models.ForeignKey(Company)
class Meta:
order_with_respect_to = 'user'
def __repr__(self):
return '<Worker {0.id}'.format(self)
def __str__(self):
return self.user.get_fullname()
The problem
I would like to add a ManyToMany relationship between Project and Worker so that I can view
a list of workers under a certain project. However, I want to make sure that a worker can only
be added to a project if they are both part of the same company.
I was planning on using a junction table with a ForeignKey to both of their company attributes,
but according to the django docs, a foreignkey can only be used once per model
(https://docs.djangoproject.com/en/1.10/topics/db/models/#extra-fields-on-many-to-many-relationships)
How do I make sure that the many to many relationship between the two tables is limited to the same company?
Is there perhaps another way to ensure that workers cannot work on projects outside of their own company?
Assuming you define the many to many relationship this way in the Project model:
workers = ManyToManyField(Worker)
Assuming you have a model form named ProjectForm to create or modify projects. You can define a clean function in this form:
def clean(self):
cleaned_data = super(ProjectForm, self).clean()
for w in cleaned_data['workers']:
if w.company.id != cleaned_data['company'].id:
self.add_error('workers', your_error_message)
break
return cleaned_data
Hope this help.

Add parent field in django admin forms

Here are my two models:
class Provider(models.Model):
name = models.CharField(max_length=75, null=True, blank=True)
code = models.CharField(max_length=15)
provider_parent = models.ForeignKey('self', null=True, blank=True)
accounts = models.ManyToManyField('Account', blank=True)
data_types = models.ManyToManyField('DataType', blank=True,
through='ProviderDataType')
class Account(models.Model):
name = models.CharField(max_length=75, unique=True)
prefixes = models.ManyToManyField('AccountPrefix', blank=True)
Here is my admin.py
class ProviderAdmin(admin.ModelAdmin):
list_display = ('code', '__unicode__')
class AccountAdmin(admin.ModelAdmin):
list_display = ('__unicode__')
admin.site.register(Provider, ProviderAdmin)
admin.site.register(Account, AccountAdmin)
I was wondering if it is possible to have a selection of the parent provider when I try to add or update my account model and upon saving it. The Parent model has already set the account on its manytomany field
If I understood your question correctly you can use TubularInline. Like this:
class ProviderInline(admin.TabularInline):
model = Provider.accounts.through
extra = 1
class AccountAdmin(admin.ModelAdmin):
inlines = [ProviderInline,]
...

Dynamic Fields with ManyToMany in Django admin

I want to define some fields for my model in another model. Here:
class Setting(models.Model):
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
class Option(models.Model):
name = models.CharField(max_length=255)
setting = models.ForeignKey(Setting)
def __unicode__(self):
return self.name
class Car(models.Model):
hsn = models.PositiveIntegerField("HSN", max_length=4)
tsn = models.PositiveIntegerField("TSN", max_length=3)
mileage = models.PositiveIntegerField("Kilometerstand")
settings = models.ManyToManyField(Setting)
In the admin I want to have every Settings.name as a field in CarAdmin with a select box of Options.name
How can I do this?
I've run accross a similar problem and solved it as below, I believe it should do the trick.
What you want is to have the Settings as inlines in your Car changeform and a M2M field pointing toward Options in the Setting Inline, overriding its widget to display it as checkboxes.
In your models.py:
class Option(models.Model):
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
class Car(models.Model):
hsn = models.PositiveIntegerField("HSN", max_length=4)
tsn = models.PositiveIntegerField("TSN", max_length=3)
mileage = models.PositiveIntegerField("Kilometerstand")
def __unicode__(self):
return self.hsn
class Setting(models.Model):
name = models.CharField(max_length=255)
options = models.ManyToManyField(Option, blank=True, null=True)
car = models.ForeignKey(Car, blank=True, null=True)
def __unicode__(self):
return self.name
In your admin.py :
from django.forms import CheckboxSelectMultiple
class OptionAdmin(admin.ModelAdmin):
pass
admin.site.register(Option, OptionAdmin)
class SettingInline(admin.TabularInline):
model = Setting
formfield_overrides = {
models.ManyToManyField: {'widget': CheckboxSelectMultiple},
}
class CarAdmin(admin.ModelAdmin):
inlines = [
SettingInline
]
admin.site.register(Car, CarAdmin)
There might be some caveats with this solution, such as common options for every setting or misplaced help text below checkboxes, I've not looked further but it should be fixable.

Categories

Resources