display foreign key in parent class - python

I could not show reverse relation in Django Rest Framework. Here in my case, a rent can have multiple images and i want to display all the images of that rent(living room image, kitchen image, bathroom image etc) in /api/v1/rent, i mean in rent resource.
So, how do I fetch these Galleries to display in the Rental resource?
class Rental(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=300, blank=False, null=False)
phone_number = models.PositiveIntegerField(null=False, blank=False)
renter = models.CharField(choices=RENTER_CHOICES, max_length=1, default=RENTER_CHOICES[0])
def __str__(self):
return self.name
def user_directory_path(instance, filename):
return 'rent_{0}/{1}'.format(instance.rent.id, filename)
class Gallery(models.Model):
rent = models.ForeignKey(Rental, related_name="rent")
image = models.FileField(upload_to=user_directory_path)
tag = models.CharField(max_length=1, choices=TAGS, null=True, blank=True)
class GallerySerializer(serializers.ModelSerializer):
rent = serializers.ReadOnlyField()
class Meta:
model = Gallery
fields = ('rent', 'image', 'tag',)
class RentalSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
# gallery = serializers.SerializerMethodField('get_children')
#
# def get_children(self, obj):
# print ('obj', obj.gallery_set)
# serializer = GallerySerializer(obj.gallery_set.all(), many=True)
# return serializer.data
gallery = GallerySerializer(source='gallery_set',many=True, read_only=True)
class Meta:
model = Rental
fields = ('user', 'name', 'phone_number','gallery',)
Right now i dont get the list of gallery, using both way, one commented way and another source='gallery_set' way.
UPDATE
if i do rent = GallerySerializer(many=True) i get the list of galleries but does it make any sense?
[
{
"user": "admin",
"name": "Sunrise Home",
"phone_number": 9842333833,
"rent": [
{
"image": "http://localhost:8000/media/rent_1/sittingRoom.jpg",
"tag": "L"
}
]
}
]
in above api you see the galleries are shown but the name shows it as a list of rent inside rental resource. Can anyone help me to design an api better way?

When you specify the foreign key relationship for Rental in your Gallery model the related_name you specify automatically defines a field on the Rental object. The name of that field is whatever you set related_name to, in this case rent.
The important thing to understand here is that the related_name will be attached to the opposite side of the relationship from where the key is declared in your models. Because the related name is going to be a property of a Rental object, a better name for it might be galleries in your example.
Assuming you change the related_name in your models from rental to galleries, you can define your Rental serializer to output a list of associated galleries:
class RentalSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
galleries = GallerySerializer(many=True, read_only=True)
class Meta:
model = Rental
fields = ('user', 'name', 'phone_number','galleries')
By declaring a GallerySerializer with the same name as the related_name defined in your foreign key relationship, it will automatically find only galleries which are associated with the Rental being serialized.
In another example:
Let's say you have several Eggs in a Basket. We would want to create a many-to-one relationship between Eggs and a Basket and would accomplish this by creating a foreign-key relationship between the two. That would be in the form of storing the ID of the associated Basket for each Egg.
In Django, we would declare that foreign key on the Egg model. This allows us to access the basket that an egg is in via egg.basket. We also want to determine what eggs are in a particular basket. We do that by defining a related_name on the foreign-key field. This tells Django what the field linking the basket to all its contained eggs should be called. Since this field refers to all eggs in a basket, we would call it eggs.
In code:
class Basket(models.Model):
color = models.CharField()
class Egg(models.Model):
color = models.CharField()
basket = models.ForeignKey(Basket, related_name="eggs")

Related

Django - How to render a ModelForm with a Select field, specifying a disabled option?

I have the following models:
# Get or create a 'Not selected' category
def get_placeholder_categoy():
category, _ = ListingCategories.objects.get_or_create(category='Not selected')
return category
# Get default's category ID
def get_placeholder_category_id():
return get_placeholder_categoy().id
class ListingCategories(models.Model):
category = models.CharField(max_length=128, unique=True)
def __str__(self):
return f'{self.category}'
class Listing(models.Model):
title = models.CharField(max_length=256)
seller = models.ForeignKey(User, on_delete=models.CASCADE, related_name='listings')
description = models.TextField(max_length=5120, blank=True)
img_url = models.URLField(default='https://media.istockphoto.com/vectors/no-image-available-picture-coming-soon-missing-photo-image-vector-id1379257950?b=1&k=20&m=1379257950&s=170667a&w=0&h=RyBlzT5Jt2U87CNkopCku3Use3c_3bsKS3yj6InGx1I=')
category = models.ForeignKey(ListingCategories, on_delete=models.CASCADE, default=get_placeholder_category_id, related_name='listings')
creation_date = models.DateTimeField()
base_price = models.DecimalField(max_digits=10, decimal_places=2, validators=[
MinValueValidator(0.01),
MaxValueValidator(99999999.99)
])
With these, I have the following form:
class ListingForm(ModelForm):
class Meta:
model = Listing
exclude = ['seller', 'creation_date']
widgets = {
'title': TextInput(attrs=base_html_classes),
'description': Textarea(attrs=base_html_classes),
'img_url': URLInput(attrs=base_html_classes),
'category': Select(attrs=base_html_classes),
'base_price': NumberInput(attrs=base_html_classes)
}
One of the available categories I have is "Not selected", since I want to allow that if at some point a category were to be removed, items can be reassigned to that one, however, when rendering the form, I will do some validation on the view function to prevent it from being submitted if the "not selected" category is sent with the form.
Because of this, I want the HTML form on the template to assign the 'disabled' attribute to the option corresponding to that category, however, I have been searching for a couple of days now without finding anything that I was able to understand to the point where I could try it.
Ideally, another thing I'd like to achieve is to be able to modify the order of the rendered options on the form so that I can move to the top 'not selected' regardless of its primary key within the model.
I am aware I can just create a form instead of a model form, or just modify the template so I manually specify how to render the form itself, but I do feel like there is a simple fix to this either on the model or on the model form that I am just not finding yet.
Thanks in advance!
I would suggest you use (in model definition)
class Listing(models.Model):
..
category = model.ForeignKey(ListingCategories, on_delete=models.SET_NULL, null=True, related_name='listings')
..
and optionally in form definition
class ListingForm(ModelForm):
category = forms.ModelChoiceField(ListingCategories, empty_label='Not Selected')
..
While rendering model form, a required attribute will be automatically added, and in form validating, it is also required. It is only in database validation that the field can be left NULL

m2m 'through' field in django models throwing this error: 'M2M field' object has no attribute '_m2m_reverse_name_cache'

Hey guys I am trying to add a m2m through field to have assistants to my 'Department' model to call like department.assistants.all(), but while doing so, I am getting this error AttributeError: 'ManyToManyField' object has no attribute '_m2m_reverse_name_cache'.
This is my model:
class Department(models.Model):
id = models.BigAutoField(primary_key=True)
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
assistants = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Assistants', related_name='dep_assistants',
symmetrical=False)
class Assistants(models.Model):
id = models.BigAutoField(primary_key=True)
department = models.ForeignKey(Department, related_name='of_department', on_delete=models.CASCADE)
assistant = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='dt_assistant',
verbose_name="Department Assistant", on_delete=models.CASCADE)
added = models.DateTimeField(auto_now_add=True)
I am pretty new to this concept. Can someone tell me what I did wrong here?
Thanks
The way you have defined your models the queries seem too confusing. Try how models are defined below and then try the query.
You did not mention the through_field attribute in the many to many field definition. check the docs: https://docs.djangoproject.com/en/3.2/ref/models/fields/#django.db.models.ManyToManyField
class Department(models.Model):
# i think this is not needed. Also id is a protected keyword in python.
# id = models.BigAutoField(primary_key=True)
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
assistants = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Assistants',
related_name='departments', through_fields=("department", "assistant"))
# model name should never be prural. It is singluar becuase it is the name of the object.
class Assistant(models.Model):
# i think this is not needed. Also id is a protected keyword in python.
# id = models.BigAutoField(primary_key=True)
department = models.ForeignKey(Department, on_delete=models.CASCADE)
assistant = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name="Department Assistant", on_delete=models.CASCADE)
added = models.DateTimeField(auto_now_add=True)
# how to query assistants from departments
# you will get objects of User model
qs = department.assistants.all()
# how to query departments from assistants
# you will get objects of Department model
qs = user.departments.all()
# If you want to query the Assistant model
# from department object
qs = department.assistant_set.all()
# from assistant object
qs = user.assistant_set.all()
# in either case you will get the objects of Assistant model
for i in qs:
print(i.added, i.department, i.assistant)
Try this and let me know if you still get the error.
My suggestion is to name the assistant field on the Assistant model as user. This way you will not need to define through_field on the many to many field.
If one assistant relates to only one department - this is relation one-to-many. (One department has many assistants) In code would be:
class Assistant(models.Model):
...
department = models.ForeignKey(Department)
No need for a special reference on Department. To get all assistants:
assistants = models.Assistant.objects.filter(department=department)
Or create a property on a class Department:
#property
def assistants(self):
return models.Assistant.objects.filter(department=self)
If one assistant relates to many departments (and each department has many assistants), it is many-to-many relationship and there should be additional class between them:
class Assignment(models.Model):
assistant = models.ForeignKey(Assistant)
department = models.ForeignKey(Department)
class Department(models.Model):
...
assignment= models.ForeignKey(Assignment)
class Assistant(models.Model):
...
assignment = models.ForeignKey(Assignment)
So here to query assistants of the department:
assistants = models.Assistant.objects.filter(
assignment__in=models.Assignment.objects.filter(
department=department
)
)

Not sure I understand dependancy between 2 django models

I am struggling to understand django models relationship.
I have this arborescence:
A train have cars, and those cars are divided into parts. Then those parts all contains different references.
Like, for exemple, all the trains have the 6 cars, and the cars 6 parts. Each part have x reference to be associated.
I would like to use all of them in a template later on, where the user can select the train, the car and the part he worked on, then generate a table from his selections with only the references associated to the parts he selected.
It should update the train and the car (I'm trying to update a stock of elements for a company)
I dont really understand which model field give to each of them. After checking the doc, Ive done something like this but i am not convinced:
class Train(Car):
train = models.CharField(max_length=200)
id = models.CharField(primary_key='True', max_length=100)
selected = models.BooleanField()
class Meta:
abstract = True
class Car(Part):
car = models.CharField(max_length=200)
id = models.CharField(primary_key='True', max_length=100)
selected = models.BooleanField()
class Meta:
abstract = True
class Part(Reference):
part = models.CharField(max_length=200)
id = models.CharField(primary_key='True', max_length=100)
selected = models.BooleanField()
class Meta:
abstract = True
class Reference(models.Model):
reference = models.CharField(max_length=200)
id = models.CharField(primary_key='True', max_length=100)
selected = models.BooleanField()
def __str__(self):
return self.reference
Can someone please help me understand this so I can do well ? Thanks!!
1-)if you add abstract = True in your Model Meta class, your class doesn't created on database as a table. If you store data for any class, you mustn't define abstract = True.
2-)For relations, you can use models.ForeignKey . If you add a class into brackets of another class, it names: inheritance.(You can think like parent-child relation). In database management, we can use foreignkey for one-to-many relationship.
3-)In Django ORM, id field automatically generated. So you don't need to define id field.
If I understand correctly, also you want to store parts of user's selected.
So, your model can be like that:
class Train(models.Model):
name = models.CharField(max_length=200) # I think you want to save name of train
class Car(models.Model):
train = models.ForeignKey(Train,on_delete=models.Cascade)
name = models.CharField(max_length=200)
class Part(models.Model):
car = models.ForeignKey(Car,on_delete=models.Cascade)
name = models.CharField(max_length=200)
class Reference(models.Model):
part = models.ForeignKey(Part,on_delete=models.Cascade)
name = models.CharField(max_length=200)
def __str__(self):
return self.reference
#addtional table for storing user's references
class UserReference(models.Model):
user = models.ForeignKey(User,on_delete=models.Cascade)
reference = models.ForeignKey(Reference,on_delete=models.Cascade)
name = models.CharField(max_length=200)
With this definitions, you can store user's definition on UserReference table. And with Django Orm, you can access train object from UserReferenceObject.
#user_reference: UserReference object like that result of UserReference.objects.first()
user_reference.reference.part.car.train.name

Pulling several Django models together into a single list

I have a MySQL database with four related tables: project, unit, unit_equipment, and equipment. A project can have many units; a unit can have many related equipment entries. A single unit can only belong to one project, but there is a many-to-many between equipment and unit (hence the unit_equipment bridge table in the DB). I'm using Django and trying to create a view (or a list?) that shows all 3 models on the same page, together. So it would list all projects, all units, and all equipment. Ideally, the display would be like this:
Project --------- Unit ------------- Equipment
Project 1 first_unit some_equipment1, some_equipment2
Project 1 second_unit more_equipment1, more_equipment2
Project 2 another_unit some_equipment1, more_equipment1
Project 2 and_another_unit some_equipment2, more_equipment2
but at this point I'd also be happy with just having a separate line for each piece of equipment, if comma-separating them is a pain.
Although it seems straightforward to create a form where I can add a new project and add related unit and equipment data (using the TabularInline class), I cannot for the life of me figure out how to bring this data together and just display it. I just want a "master list" of everything in the database, basically.
Here's the code I have so far:
models.py
class Project(models.Model):
name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = False
db_table = 'project'
def __str__(self):
return self.name
class Unit(models.Model):
project = models.ForeignKey(Project, models.DO_NOTHING, blank=True, null=True)
name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = False
db_table = 'unit'
def __str__(self):
return self.name
class UnitEquipment(models.Model):
unit = models.ForeignKey(Unit, models.DO_NOTHING, blank=True, null=True)
equipment = models.ForeignKey(Equipment, models.DO_NOTHING, blank=True, null=True)
class Meta:
managed = False
db_table = 'unit_equipment'
class Equipment(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
description = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = False
db_table = 'equipment'
def __str__(self):
return self.name
views.py
def project_detail_view(request):
obj = Project.objects.all()
context = {'object': obj}
return render(request, "project/project_detail.html", context)
urls.py
urlpatterns = [
path('project/', project_detail_view),
path('', admin.site.urls),
]
admin.py
class UnitTabularInLine(admin.TabularInline):
model = Unit
extra = 0
class ProjectAdmin(admin.ModelAdmin):
inlines = [UnitTabularInLine]
class Meta:
model = Project
# a list of displayed columns name.
list_display = ['name']
# define search columns list, then a search box will be added at the top of list page.
search_fields = ['name']
# define filter columns list, then a filter widget will be shown at right side of list page.
list_filter = ['name']
# define model data list ordering.
ordering = ('name')
I think I need to somehow add more entries to the list_display in the admin file, but every time I try to add unit or equipment it throws an error. I've also tried adding more attributes to Project, but I can't seem to get the syntax right, and I'm never sure which model class I'm supposed to make it.
I've also looked at FormSets, but I cannot get my head around how to alter my current code to get it to work.
How do I get these models together into a unified view?
You don't need to edit the admin view to add your own view: which you may find you are able to do in this case to get your data displayed exactly as you want.
If you do want to show the related object values in the admin list, then you can use lookups and custom columns: however in this case your list would be based upon the Unit.
# You don't need an explicit UnitEquipment model here: you can
# use a simple ManyToManyField
class Unit(models.Model):
project = ...
name = ...
equipment = models.ManyToManyField(Equipment, related_name='units')
def equipment_list(admin, instance):
return ', '.join([x.name for x in instance.equimpent.all()])
class UnitAdmin(admin.ModelAdmin):
class Meta:
model = Unit
list_display = ['project__name', 'name', equipment_list]
def get_queryset(self, request):
return super().get_queryset(request)\
.select_related('project')\
.prefetch_related('equipment')
Note that you need to have the queryset override, otherwise there will be a bunch of extra queries as each unit also requires fetching the project and list of equipment for that unit.
There's also a further improvement you can make to your queries: you could aggregate the related equipment names using a Subquery annotation, and prevent the second query (that fetches all related equipment items for the units in the queryset). This would replace the prefetch_related()
Thanks to #Matthew Schinckel, I was able to find my way to the answer. Here's what my files look like now (only edited the Unit class in models.py):
models.py
class Unit(models.Model):
project = models.ForeignKey(Project, models.DO_NOTHING, blank=True, null=True)
name = models.CharField(max_length=255, blank=True, null=True)
equipment = models.ManyToManyField(Equipment, related_name='units')
class Meta:
managed = False
db_table = 'unit'
def __str__(self):
return self.name
def equipment_list(self):
return ', '.join([x.name for x in self.equipment.all()])
admin.py
class UnitAdmin(admin.ModelAdmin):
class Meta:
model = Unit
# a list of displayed columns name.
list_display = ('project', 'name', 'equipment_list')
# define search columns list, then a search box will be added at the top of list page.
search_fields = ['project']
# define filter columns list, then a filter widget will be shown at right side of list page.
list_filter = ['project', 'name']
# define model data list ordering.
ordering = ('project', 'name')
def get_queryset(self, request):
return super().get_queryset(request)\
.select_related('project')\
.prefetch_related('equipment')
So the changes I made were:
1. Make list_display a tuple instead of a list.
2. Throw def equipment_list(self) into the Unit class (so it's callable as an attribute of Unit) and pass (self) instead of (admin, instance) (I kept getting an error that was looking for the instance argument).

Filtering by foreign key in dropdown

I'm using django-filters.
My Car model has a Manufacturer foreign key . What I want to do is Filter the Cars by a dropdown that is populated with all Manufacturers in the database.
class Car(models.Model):
slug = models.SlugField(unique=True)
name = models.CharField(max_length=256)
cost = models.DecimalField(max_digits=10,decimal_places=5)
manufacturer = models.ForeignKey(Manufacturer)
My filter currently is a blank text field, you can enter a manufacturer name and then submit to filter this way. The Dropdown would be much more suitable, but I haven't been able to find a way to do this. Here is the filter model as it is now:
class CarFilter(django_filters.FilterSet):
manufacturer = django_filters.CharFilter(name="manufacturer__name")
class Meta:
model = Car
fields = ['name', 'manufacturer']
Just don't define the manufacturer filter field at all and django-filter will use the default filter for this field (which is a drop down). So something like this should be working:
class CarFilter(django_filters.FilterSet):
class Meta:
model = Car
fields = ['name', 'manufacturer']

Categories