In Django Admin searching by a Relational Key(1to1) - django

I have two models Owner and Entity with OneToOne Relationship.
class Owner(models.Model):
name = models.CharField(max_length=255)
.....
def __str__(self):
return self.name
class Entity(models.Model):
owner = models.OneToOneField(Owner, blank=True, null=True, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
......
For Django Admin, search fields I have:
class EntityAdmin(admin.ModelAdmin):
.....
search_fields = ('email', 'name', 'owner')
If I try to search I get the following error:
Related Field got invalid lookup: icontains
If I remove owner, but I still want to search by owner

As the docs show, you need to follow the relationship to an actual text field. So:
search_fields = ('email', 'name', 'owner__name')

Related

Django UniqueConstraint doesn't works with ManyToManyField, got exception FieldDoesNotExist

The migration stopped working when switching from ForeignKey between Shop and user to a ManyToManyField. I wanted shops to be able to be owned by different users at the same time:
class Shop(models.Model):
name = models.CharField('name', max_length=120)
#user = models.ForeignKey(User, on_delete=models.CASCADE) ##before
shopuser= models.ManyToManyField(User, related_name="shopusers", blank=True) ##after
class Meta:
constraints = [models.UniqueConstraint(fields=['shopuser', 'name'], name='user cant have the same shop twice!')]
## after:
#property
def get_shopuser(self):
return ", ".join([u.username for u in self.shopuser.all()])
class Warehouse(models.Model):
address = models.CharField('address', max_length=120)
user = models.ManyToManyField(User, related_name="User", blank=True)
django.core.exceptions.FieldDoesNotExist: NewShop has no field named 'shopusers'
I thought by choosing a related name I can use multiple relations to the User model? I already tried completely deleting my database (and migrations folder) and migrate from start, but tht did not help :(
Example where I would call the code:
admin.py:
#admin.register(Shop)
class ShopAdmin(admin.ModelAdmin):
list_display = ("name", "related_shopuser")
list_filter = ("name", "shopuser")
fieldsets = [("Requrired Information", {"description": "These fields are required",
"fields": (("name", "shopuser"))}),]
def related_shopuser(self, obj):
return obj.get_shopuser
Where does Djanog migrations get the NewShop from FieldDoesNotExist("%s has no field named '%s'" % (self.object_name, field_name))? Is it automatically generated from the ModelName Shop?
Issue
The UniqueConstraint on ManyToManyField will not work as expected. If you want to enforce the constraint, you should define a intermediate model and connect them using the through--(Django doc) parameter.
For the sake of simplicity, I assume you have a model as below,
class Shop(models.Model):
name = models.CharField('name', max_length=120)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(
fields=[
'user',
'name'
],
name='user cant have the same shop twice!'
)
]
and now you want make the user field to a ManyToManyField from ForeignKey
Note: I have changed the field name to users from user, which is more approriate.
Method-1 : Remove unique constraint
class Shop(models.Model):
name = models.CharField('name', max_length=120)
users = models.ManyToManyField(User)
Now, run makemigrations and migrate commands
Method-2: use an intermediate model
class ShopUserIntermediateModel(models.Model):
shop = models.ForeignKey('Shop', models.CASCADE)
user = models.ForeignKey(User, models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(
fields=[
'shop',
'user'
],
name='user cant have the same shop twice!'
)
]
class Shop(models.Model):
name = models.CharField('name', max_length=120)
users = models.ManyToManyField(User, through=ShopUserIntermediateModel)
If not required (the project is already in production), try deleting your migration files, or at least the migration where the user field was added in the first time

Django Rest Framework - how to get only one field from related models set

I have following models:
from django.db import models
class City(models.Model):
name = models.CharField(max_length=30)
last_update = models.DateTimeField(null=True)
class BusStop(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
name = models.CharField(max_length=200, blank=True, default='')
Now using Django Rest Framework, I would like to create serializer that will return City details along with the list of all BusStops in the city - but I want the list to be only strings with BusStop names, like this:
{
"id": 1
"name": "City"
"last_update": "2019-09-19T22:13:54.851363Z"
"bus_stops": [
"stop1",
"stop2",
"stop3"
]
}
What I've tried so far is following serializers:
from rest_framework import serializers
class BusStopSerializer(serializers.ModelSerializer):
class Meta:
model = BusStop
fields = ('name', )
class CityDetailsSerializer(serializers.ModelSerializer):
busstop_set = BusStopSerializer(many=True)
class Meta:
model = City
fields = ('id', 'name', 'last_update', 'busstop_set')
But this creates list of objects with 'name' in them. So, how can I create a list with only BusStop names (as strings) in it?
Instead of the extra BusStopSerializer you could use a StringRelatedField:
# models.py
class BusStop(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
name = models.CharField(max_length=200, blank=True, default='')
def __str__(self):
return self.name
# serializers.py
class CityDetailsSerializer(serializers.ModelSerializer):
bus_stops = StringRelatedField(many=True)
class Meta:
model = City
fields = ('id', 'name', 'last_update', 'bus_stops')
StringRelatedField, as recommended by wfehr, will only work as long as the __str__ method of BusStop only returns the name. An alternative is to use SlugRelatedField which allows you to specify a particular field from the related model, and has no dependency on __str__.
bus_stops = SlugRelatedField(many=True, slug_field='name')

Store the IDs in the Parent Model of the Child Models which are currently related via ForeignKeys

I have three models, currently i am using an url like so to do updates and get content:
http://localhost:8000/manuscripts-api/manuscriptlibrary/28/
My question relates to the approach i should use so that i can include in my ManuscriptItem model the IDs of the related Libraries and Settings. What kind of fields can i add to the ManuscriptItem model that does this?
My models:
class ManuscriptItem(models.Model):
"""Represents a single manuscript's content"""
author = models.ForeignKey('accounts_api.UserProfile', on_delete=models.CASCADE)
title = models.CharField(max_length=255, blank=True)
content = models.CharField(max_length=99999999, blank=True)
def __str__(self):
"""Django uses when it needs to convert the object to a string"""
return str(self.id)
class ManuscriptLibrary(models.Model):
"""Represents a single manuscript's library"""
manuscript = models.OneToOneField(ManuscriptItem, on_delete=models.CASCADE)
bookmarks = models.CharField(max_length=99999999)
history = models.CharField(max_length=99999999)
def __str__(self):
"""Django uses when it needs to convert the object to a string"""
return str(self.manuscript)
class ManuscriptSettings(models.Model):
"""Represents a single manuscript's settings"""
manuscript = models.OneToOneField(ManuscriptItem, on_delete=models.CASCADE)
citation_suggestions = models.BooleanField(default=False)
terminology_suggestions = models.BooleanField(default=False)
paper_suggestions = models.BooleanField(default=False)
def __str__(self):
"""Django uses when it needs to convert the object to a string"""
return str(self.manuscript)
My serializers:
class ManuscriptItemSerializer(serializers.ModelSerializer):
"""A serializer for manuscript items."""
class Meta:
model = models.ManuscriptItem
fields = ('id', 'author', 'title', 'content')
extra_kwargs = {'author': {'read_only': True}}
class ManuscriptLibrarySerializer(serializers.ModelSerializer):
"""A serializer for a manuscript's library."""
class Meta:
model = models.ManuscriptLibrary
fields = ('id', 'manuscript', 'bookmarks', 'history')
class ManuscriptSettingsSerializer(serializers.ModelSerializer):
"""A serializer for a manuscript's settings."""
class Meta:
model = models.ManuscriptSettings
fields = ('id', 'manuscript', 'citation_suggestions', 'terminology_suggestions', 'paper_suggestions')
You don't necessarily need to add any new fields to the ManuscriptItem model. You can access the id of the related ManuscriptLibrary and ManuscriptSettings objects by defining the related_name property of the foreign key.
class ManuscriptLibrary(models.Model):
manuscript = models.OneToOneField(ManuscriptItem, on_delete=models.CASCADE, related_name='library')
class ManuscriptSettings(models.Model):
manuscript = models.OneToOneField(ManuscriptItem, on_delete=models.CASCADE, related_name='setting')
Once this is migrated, you can use manuscript_item.library to access the related library object, and manuscript_item.setting to access the related setting. Accessing ids can be done via manuscript_item.library.id.
Edit: To display the ids in the serialized object, you can modify your ManuscriptItemSerializer as given below
class ManuscriptItemSerializer(serializers.ModelSerializer):
library = ManuscriptLibrarySerializer(required=False)
setting = ManuscriptSettingsSerializer(required=False)
class Meta:
model = models.ManuscriptItem
fields = ('id', 'author', 'title', 'content', 'library', 'setting', )
by the docs one_to_one
your ManuscriptItem instance has two property manuscriptlibrary -- instance of the ManuscriptLibrary model and manuscriptsettings instance of the ManuscriptSettings model. So you can get the id by manuscriptlibrary.pk and manuscriptsettings.pk, but may be best solution for greater readability you can use related_name as arjun27 write.

Django admin: list_display/search_fields foreign key of a foreign key in admin view

I have a question for django programmer that should be quite easy but at the moment I cannot figure out how to solve.
I have these three models (I simplify as much as I can):
class Nations(models.Model):
label = models.CharField(max_length=200)
iso2 = models.CharField(max_length=2, unique=True)
class Cities(models.Model):
label = models.CharField(max_length=200, null=True)
country_code = models.ForeignKey(Nations, to_field='iso2', on_delete=models.SET_NULL, null=True, verbose_name='Nation')
class Person(models.Model):
username = models.CharField(max_length=200, blank=False)
city = models.ForeignKey(Cities, on_delete=models.SET_NULL, null=True, blank=True)
As you can see, Person model is just connected with Cities model. What I need to do is to set PersonAdmin class in order to add in the admin view a column showing Nations.label value and make it searchable. In the example below I called this field city__country_code__label, just to make you figure out what I mean (but of course it doesn't work because there is no country_code object in Person model).
class PersonAdmin(admin.ModelAdmin):
list_display = ('id', 'username', 'city', 'city__country_code__label')
ordering = ('username',)
raw_id_fields = ('city',)
search_fields = ['username', 'city__label', 'city__country_code__label']
[...]
how can I ask Django to do this?
Thanx in advance!
Add a method to your model admin that takes a person obj and returns the country label. Then add the method to list_display.
class PersonAdmin(admin.ModelAdmin):
def country_label(self, obj):
return obj.city.country_code.label
list_display = ('id', 'username', 'city', 'country_label')
list_select_related = ['city__country_code']
ordering = ('username',)
raw_id_fields = ('city',)
search_fields = ['username', 'city__label', 'city__country_code__label']
See the list_display docs for more info.
Note I have used list_select_related to reduce the number of SQL queries.

Properly using Foreign Key references in search_fields, Django admin

I've got a weird conundrum that I need some help with in Django 1.8.4 using python 3.4 in a virtual-env.
I've got 2 models in 2 different apps... as follows with multiple Foreign Key references.
Inventory App
class InventoryItem(models.Model):
item_unique_code = models.CharField(max_length=256, blank=False, null=False)
category = models.CharField(max_length=256, blank=False, null=False,choices=[('RAW','Raw Material'),('FG','Finished Good'),('PKG','Packaging')])
name = models.CharField(max_length=64, blank=False, null=False)
supplier = models.CharField(max_length=96, blank=False,null=False)
approved_by = models.CharField(max_length=64, editable=False)
date_approved = models.DateTimeField(auto_now_add=True, editable=False)
comments = models.TextField(blank=True, null=True)
def __str__(self):
return "%s | %s | %s" % (self.item_unique_code,self.name,self.supplier)
class Meta:
managed = True
unique_together = (('item_unique_code', 'category', 'name', 'supplier'),)
Recipe App
class RecipeControl(models.Model):
#recipe_name choice field needs to be a query set of all records containing "FG-Finished Goods"
recipe_name = models.ForeignKey(items.InventoryItem, related_name='recipe_name', limit_choices_to={'category': 'FG'})
customer = models.ForeignKey(customers.CustomerProfile, related_name='customer')
ingredient = models.ForeignKey(items.InventoryItem, related_name='ingredient')
min_weight = models.DecimalField(max_digits=16, decimal_places=2, blank=True, null=True)
max_weight = models.DecimalField(max_digits=16, decimal_places=2, blank=True, null=True)
active_recipe = models.BooleanField(default=False)
active_by = models.CharField(max_length=64, editable=False)
revision = models.IntegerField(default=0)
last_updated = models.DateTimeField(auto_now_add=True, editable=False)
def __str__(self):
return "%s" % (self.recipe_name)
class Meta:
managed = True
unique_together = (('recipe_name', 'customer', 'ingredient'),)
I've been getting some weird results in my Recipe's Admin class...
from django.contrib import admin
from django.contrib.auth.models import User
from .models import RecipeControl
from Inventory import models
class RecipeView(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.active_by = request.user.username
obj.save()
fieldsets = [
('Recipe Information', {'fields': ['recipe_name', 'customer']}),
('Ingredients', {'fields': ['ingredient','min_weight','max_weight','active_recipe']}),
('Audit Trail', {'fields': ['active_by','revision','last_updated'],'classes':['collaspe']}),
]
list_select_related = ['recipe_name','customer','ingredient']
search_fields = ['recipe_name','customer','ingredient','active_by']
readonly_fields = ('last_updated','active_by')
list_display = ['recipe_name','customer','ingredient','min_weight','max_weight','last_updated','active_by', 'active_recipe']
admin.site.register(RecipeControl, RecipeView)
The issue I've come across is if I try to do a search on any ForeignKey field, Django throws this error...
Exception Type: TypeError at /admin/Recipe/recipecontrol/
Exception Value: Related Field got invalid lookup: icontains
According to the Django Admin Doc's and other older questions on stackoverflow on the subject it says I should be doing something along the lines of search_fields = ['inventoryitem__name'] but I think this is in reference to FK's in the same app model.py.
Is there a more correct way of referencing/importing other models from other apps that I'm missing or do I have to use some kind of callable method magic to get the search function to look up correctly? I've tried a multitude of different combinations and nothing seems to work. I'm relatively new to Django so I'm confident it's something simple.
You should use the double underscore notation to search a field on a related object. However, you should use the name of the foreign key field (e.g. recipe_name), not the name of the model (e.g. InventoryItem). It doesn't matter whether or not the foreign key's model is in the same app. For example:
search_fields = ['recipe_name__name']
Note that if you want to search the recipe_name and ingredient fields, you need to include both fields, even though they are foreign keys to the same model.
search_fields = ['recipe_name__name', 'ingredient__name']