Search field on generic foreign key field - django

I am trying to add a search field to the Django admin model CreditsAdmin that will allow me to search the email of related customer objects. The Customer object has a generic foreign key to many different typed of object all of which have an email.
I've already tried defining the function customer_email on the Customer object and using it as a search field, but this gives the error Related Field got invalid lookup: customer_email
class Customer(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
#property
def customer_email(self):
return str(self.content_object.email)
class Credits(models.Model):
credits_remaining = models.IntegerField()
current_period_start = models.DateTimeField()
current_period_end = models.DateTimeField()
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
class CreditsAdmin(admin.ModelAdmin):
list_display = (
'current_period_start',
'current_period_end',
'customer_name',
'credits_remaining',
)
search_fields = ('customer__customer_email',)
I'd like to be able to search the emails of related generic objects on the Customer model from the CreditsAdmin interface. In particular, one of the content_types that the customer objects relate to is django's auth.User model, but there are also others.

You cannot use property in search_fields, because it looks for columns in database level.
GenericRelation might be a solution. You can create fields in related(by content type) models. For example:
class CntObject(models.Model):
customers = GenericRelation(Customer, related_query_name='cnt_objects')
And in admin panel for Credits:
class CreditsAdmin(admin.ModelAdmin):
list_display = (
'current_period_start',
'current_period_end',
'customer_name',
'credits_remaining',
)
search_fields = ('customer__cnt_objects__email',)
There is a not best thing with this answer. You must be sure that all related content_objects will have email field. May be issues with performance, didn't test it.
Other solution might be, your custom get_search_results method in admin class.

Related

When don't we use "__all__" in ModelSerializer in Django Rest Framework

This is just my curiosity but I will be very happy if anyone answers my question.
I am using Django Rest Framework but I'm a beginner. In serializers.py, I use ModelSerializer and "all" to fields attribute.
This is an example.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
And then, I just thought
when don't we use "__all__" in serializers.py??
As long as we create models.py in advance, I think we usually use all fields in each Model.
I would like you to teach me when we omit specific fields that come from each Model.
Thank you.
So the second question is a bit harder to explain in a comment:
If we use some fields of all fields in Model, how do we store information of the rest of fields?
Various cases:
Fields with defaults:
class Log(models.Model):
message = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class LogSerializer(serializers.ModelSerializer):
class Meta:
model = Log
fields = ('message',)
For autogenerated, think user profile models via the post_save signal or calculated fields:
class OrderLine(models.Model):
order = models.ForeignKey(Order)
name = models.CharField(max_length=200)
quantity = models.IntegerField()
price = models.DecimalField()
class OrderLineSerializer(serializers.ModelSerializer):
order = serializers.PrimaryKeyRelatedField()
product = serializers.IntegerField()
class Meta:
model = OrderLine
fields = ('quantity', 'product', 'order')
In this case, the product is a primary key for a product. The serializer will have a save method that looks up the product and put it's name and price on the OrderLine. This is standard practice as you cannot reference a product in your orders, else your orders would change if you change (the price of) your product.
And derived from request:
class BlogPost(models.Model):
author = models.ForeignKey(User)
post = models.TextField()
class BlogPostSerializer(serializers.ModelSerializer):
class Meta:
model = BlogPost
fields = ('post',)
def create(self, validated_data):
instance = BlogPost(**validated_data)
instance.author = self.context['request'].user
instance.save()
return instance
This is pretty much the common cases.
There are many cases, but I think the two main ones are:
When you don't want all fields to be returned by the serializer.
When you need some method of the serializer to know its fields. In such case, you should traverse fields array, but it doesn't work if you use __all__, only if you have an actual list of fields.

Django RestFramework - parent-child model serializer with DB views?

I am trying to implement a serializer that returns a parent record with its children embedded in the response json object.
My model for the parent and child are both based on database views:
class ProductContributorView(models.Model): # its a model of a view
id = models.IntegerField(primary_key=True)
product_id = models.ForeignKey('ProductTitleView', on_delete=models.DO_NOTHING, related_name='contributors')
sequenceNumber = models.IntegerField()
name = models.CharField(max_length=180)
role = models.CharField(max_length=8, null=True)
description = models.CharField(max_length=1408)
class Meta:
managed = False
ordering = ['sequenceNumber',]
class ProductTitleView(models.Model):
id = models.IntegerField(primary_key=True)
isbn = models.CharField(max_length=80)
titleText = models.CharField(max_length=300)
class Meta:
managed = False
ordering = ['titleText', 'isbn',]
Here are the serializers:
class ProductContributorViewSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ProductContributorView
fields = ('id', 'product_id', 'sequenceNumber', 'name', 'role', 'description')
def create(self, validated_data):
contributor = ProductContributorView.objects.create(
id=validated_data['id'],
product_id=validated_data['product_id'],
sequenceNumber=validated_data['sequenceNumber'],
name=validated_data['name'],
role=validated_data['role'],
description=validated_data['description'])
return contributor
class ProductTitleViewSerializer(serializers.HyperlinkedModelSerializer):
contributors = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = ProductTitleView
fields = ('id', 'isbn', 'titleText', 'contributors')
Here are the views:
class ProductTitleViewList(generics.ListAPIView):
queryset = ProductTitleView.objects.all()
serializer_class = ProductTitleViewSerializer
class ProductContributorViewList(generics.ListAPIView):
queryset = ProductContributorView.objects.all()
serializer_class = ProductContributorViewSerializer
The basic idea is to have the contributors - author, illustrator, etc - returned with the book title based on the FK in the ProductContributorView view matching the id in the ProductTitleView.
When I run this, however, I get the following error:
1054, "Unknown column 'jester_productcontributorview.product_id_id' in 'field list'"
I didn't specify product_id_id in the field list, and I've also tried referring to the field as just product in the field list, but it still repeats the _id_id suffix. Hoping someone will point me to documentation where the FK naming conventions are explained or tell me what to change in the field list. Thanks!
You may just want to try renaming that product_id ForeignKey to just product.
This hints to why it may be broken, I suspect it's breaking somewhere in the serializers inspection of your models regarding the naming of the product_id field on the model.
When you define a ForeignKey on a model there are two properties available for that field. One is the property you define, the ForeignKey object, and you should use this to get the related model. Behind the scenes Django also creates another property which appends _id to the the foreign key's name, this property represents the IntegerField on the database which stores the relation. If you were to view the table in psql you will see the _id columns (and in your case, _id_id).

How to fetch all records from 2 tables

I have 2 models
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
employee_id = models.CharField(max_length=13, unique=True)
class UserRole(models.Model):
employee_id = models.ForeignKey(CustomUser, to_field='employee_id', unique=True, on_delete=models.CASCADE)
supervisor_id = models.CharField(max_length=20, null=True)
and have defined seriallizers for both models
class UserSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = models.CustomUser
class UserRoleSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = models.UserRole
consider i have 5 records in both tables
How to fetch all records from both tables (email, employee_id, supervisor_id )
like, where CustomUser.employee_id = UserRole.employee_id
I tried with
models.CustomUser.objects.select_related('UserRole')
But, not able to fetch records from UserRole table.
UserRole.objects.select_related('employee_id')
-- this is fetching only userRole records
Thanks in Advance
You don't get automatic joins in Django, no need for it because you can just follow the ForeignKey relation on the object (the_object_you_get.its_foreign_key). Now, of course, that will hit the DB twice. if you'd rather avoid that, you can use something like .prefetch_related('employee_id') on whatever queryset you need to use. This will prevent you from hitting the DB multiple times (at the price of one larger initial query of course).
Finally, if you wanna serialize a ForeignKey relation, the answer Shakil gave is pretty comprehensive. Only thing is that you don't necessarily need to set the employee_id field as read_only=True (a bit of a limitation). You can also override the UserRoleSerializer .create() the method so that it calls the .create() method of UserSerializer.
I think from UserRole you want to all information about employe's too. What i am thinking you want to get all details information of employee_id foreign_key relation. This can be done by
UserRole.objects.all()
with the following serializers.
class UserSerializer(serializers.ModelSerializer):
class Meta:
fields = ('email','employee_id',)
model = models.CustomUser
class UserRoleSerializer(serializers.ModelSerializer):
employee_id = UserSerializer()
class Meta:
fields = ('employee_id','supervisor_id',)
model = models.UserRole

Model can have a ForeignKey with one of the two models

I need some help with an issue that I am not able to resolve on my own. So in this model, a tenancy document can either have a ForeignKey with Building or Property.
We may have a tenancy agreement on the whole building or on a single property within that building. In the former case the tenancy documents are applied to the building, and in the latter case they only apply to a property.
I used content_types to add a generic foreign key but now I can’t figure out how to add autocomplete fields to contenttype and in the dropdown,
I just see building and property in admin form. I want to see building names and property names.
I learned about autocomplete fields in Django 2.0, it’s awesome but I don’t know how can I use something like that in this particular case or if there is a better way to do this?
models.py:
class TenancyDocument(models.Model):
KINDS = Choices('Tenancy Agreement', 'Stamp Duty', 'Inventory List')
id = FlaxId(primary_key=True)
kind = StatusField(choices_name='KINDS')
start_date = models.DateField(blank=True, null=True)
end_date = models.DateField(blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
content_type_limit = Q(
app_label='properties', model='building') | Q(
app_label='properties', model='property')
content_type = models.ForeignKey(
ContentType,
limit_choices_to=content_type_limit,
on_delete=models.CASCADE,
verbose_name='Lease type'
)
object_id = FlaxId(blank=True, null=True)
content_object = GenericForeignKey('content_type', 'object_id')
def __str__(self):
return self.kind
admin.py:
#admin.register(TenancyDocument)
class TenancyDocumentAdmin(admin.ModelAdmin):
list_display = ('id', 'kind', 'start_date', 'end_date','content_type')
list_filter = ('kind',)
It seems like the generic foreign key has always been more trouble than it's worth. It takes a simple concept, a relational join, and tries to make it clever, but then downstream packages like autocomplete won't work.
I ended up switching to two separate foreign keys, then added attributes to the class to pull fields from the correct related record.
class TenancyDocument(models.Model):
building = models.ForeignKey(Building, ondelete='CASCADE', null=True, blank=True)
prop = models.ForeignKey(Property, ondelete='CASCADE', null=True, blank=True)
def clean(self):
if not self.building and not self.prop:
raise ValidationError('Must provide either building or property.')
if self.building and self.prop:
raise ValidationError('Select building or property, but not both.')
super().clean()

Using a UUID as a primary key in Django models (generic relations impact)

For a number of reasons^, I'd like to use a UUID as a primary key in some of my Django models. If I do so, will I still be able to use outside apps like "contrib.comments", "django-voting" or "django-tagging" which use generic relations via ContentType?
Using "django-voting" as an example, the Vote model looks like this:
class Vote(models.Model):
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
object = generic.GenericForeignKey('content_type', 'object_id')
vote = models.SmallIntegerField(choices=SCORES)
This app seems to be assuming that the primary key for the model being voted on is an integer.
The built-in comments app seems to be capable of handling non-integer PKs, though:
class BaseCommentAbstractModel(models.Model):
content_type = models.ForeignKey(ContentType,
verbose_name=_('content type'),
related_name="content_type_set_for_%(class)s")
object_pk = models.TextField(_('object ID'))
content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk")
Is this "integer-PK-assumed" problem a common situation for third-party apps which would make using UUIDs a pain? Or, possibly, am I misreading this situation?
Is there a way to use UUIDs as primary keys in Django without causing too much trouble?
^ Some of the reasons: hiding object counts, preventing url "id crawling", using multiple servers to create non-conflicting objects, ...
As seen in the documentation, from Django 1.8 there is a built in UUID field. The performance differences when using a UUID vs integer are negligible.
import uuid
from django.db import models
class MyUUIDModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
You can also check this answer for more information.
A UUID primary key will cause problems not only with generic relations, but with efficiency in general: every foreign key will be significantly more expensive—both to store, and to join on—than a machine word.
However, nothing requires the UUID to be the primary key: just make it a secondary key, by supplementing your model with a uuid field with unique=True. Use the implicit primary key as normal (internal to your system), and use the UUID as your external identifier.
The real problem with UUID as a PK is the disk fragmentation and insert degradation associated with non-numeric identiifers. Because the PK is a clustered index (in virtually every RDBMS except PostgreSQL), when it's not auto-incremented, your DB engine will have to resort your physical drive when inserting a row with an id of lower ordinality, which will happen all the time with UUIDs. When you get lots of data in your DB, it may take many seconds or even minutes just to insert one new record. And your disk will eventually become fragmented, requiring periodic disk defragmentation. This is all really bad.
To solve for these, I recently came up with the following architecture that I thought would be worth sharing.
The UUID Pseudo-Primary-Key
This method allows you to leverage the benefits of a UUID as a Primary Key (using a unique index UUID), while maintaining an auto-incremented PK to address the fragmentation and insert performance degredation concerns of having a non-numeric PK.
How it works:
Create an auto-incremented primary key called pkid on your DB Models.
Add a unique-indexed UUID id field to allow you to search by a UUID id, instead of a numeric primary key.
Point the ForeignKey to the UUID (using to_field='id') to allow your foreign-keys to properly represent the Pseudo-PK instead of the numeric ID.
Essentially, you will do the following:
First, create an abstract Django Base Model
class UUIDModel(models.Model):
pkid = models.BigAutoField(primary_key=True, editable=False)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
class Meta:
abstract = True
Make sure to extend the base model instead of models.Model
class Site(UUIDModel):
name = models.CharField(max_length=255)
Also make sure your ForeignKeys point to the UUID id field instead of the auto-incremented pkid field:
class Page(UUIDModel):
site = models.ForeignKey(Site, to_field='id', on_delete=models.CASCADE)
If you're using Django Rest Framework (DRF), make sure to also create a Base ViewSet class to set the default search field:
class UUIDModelViewSet(viewsets.ModelViewSet):
lookup_field = 'id'
And extend that instead of the base ModelViewSet for your API views:
class SiteViewSet(UUIDModelViewSet):
model = Site
class PageViewSet(UUIDModelViewSet):
model = Page
More notes on the why and the how in this article: https://www.stevenmoseley.com/blog/uuid-primary-keys-django-rest-framework-2-steps
I ran into a similar situation and found out in the official Django documentation, that the object_id doesn't have to be of the same type as the primary_key of the related model. For example, if you want your generic relationship to be valid for both IntegerField and CharField id's, just set your object_id to be a CharField. Since integers can coerce into strings it'll be fine. Same goes for UUIDField.
Example:
class Vote(models.Model):
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
object_id = models.CharField(max_length=50) # <<-- This line was modified
object = generic.GenericForeignKey('content_type', 'object_id')
vote = models.SmallIntegerField(choices=SCORES)
this can be done by using a custom base abstract model,using the following steps.
First create a folder in your project call it basemodel then add a abstractmodelbase.py with the following below:
from django.db import models
import uuid
class BaseAbstractModel(models.Model):
"""
This model defines base models that implements common fields like:
created_at
updated_at
is_deleted
"""
id = models.UUIDField(primary_key=True, unique=True, default=uuid.uuid4, editable=False)
created_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True, editable=False)
is_deleted = models.BooleanField(default=False)
def soft_delete(self):
"""soft delete a model instance"""
self.is_deleted=True
self.save()
class Meta:
abstract = True
ordering = ['-created_at']
second: in all your model file for each app do this
from django.db import models
from basemodel import BaseAbstractModel
import uuid
# Create your models here.
class Incident(BaseAbstractModel):
""" Incident model """
place = models.CharField(max_length=50, blank=False, null=False)
personal_number = models.CharField(max_length=12, blank=False, null=False)
description = models.TextField(max_length=500, blank=False, null=False)
action = models.TextField(max_length=500, blank=True, null=True)
image = models.ImageField(upload_to='images/', blank=True, null=True)
incident_date = models.DateTimeField(blank=False, null=False)
So the above model incident inherent all the field in baseabstract model.
The question can be rephrased as "is there a way to get Django to use a UUID for all database ids in all tables instead of an auto-incremented integer?".
Sure, I can do:
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
in all of my tables, but I can't find a way to do this for:
3rd party modules
Django generated ManyToMany tables
So, this appears to be a missing Django feature.