Autocomplete with field related to ContentType - django

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.
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?
Model:
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:
#admin.register(TenancyDocument)
class TenancyDocumnentAdmin(admin.ModelAdmin):
list_display = ('id', 'kind', 'start_date', 'end_date','content_type')
list_filter = ('kind',)

Related

Trying to access 'content_object' fields in Django under ContentType

I am trying to access the Item model through the Thing queryset, and keep getting the error:
django.core.exceptions.FieldError: Field 'content_object' does not generate an automatic reverse relation and therefore cannot be used for reverse querying. If it is a GenericForeignKey, consider adding a GenericRelation.
class ThingContent(models.Model):
content_id = models.AutoField(primary_key=True, null=False)
thing = models.ForeignKey('Thing', on_delete=models.SET_NULL, null=True)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
I have tried updating the fields on Item by adding a related_query_name to no success.
self.queryset.filter(items__item_date=exact_item_date))
class Item(models.Model):
id = models.AutoField(primary_key=True, null=False)
item_date = models.DateField(**options)
thing_content = GenericRelation('ThingContent', related_query_name='items')
content_object is generic. You must filter by object_id and content_type for know what object you FK generic.
You try again with query like this:
ThingContent.objects.filter(
items__item_date=item_date,
content_type=ContentType.objects.get_for_model(Item)
)
this query mean: find all ThingContent have mapping with Item, and have item_date like what you want filter.

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()

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']

Generic relations vs. iterating on objects in Django

I have three models called Post, Category and Flag.
I let users to make a Flag on each Post which is tagged with a Category. Now I want to list all of Flags, related to Posts on a certain Category.
I've found two ways of doing it using ListView generic view:
First approach:
Define a generic relation between Flag and Category so I can call something like: Flag.objects.filter(object__categor=self.category)
Second approach:
def get_queryset(self):
pk = self.kwargs['pk']
self.category = get_object_or_404(Category, pk=pk)
flags = Flag.objects.all()
qs=[]
for f in flags:
if f.object.category == self.category:
qs.append(f)
return qs
My question is which approach is more efficient? I'm not familiar with generic relations, but it seems a little bit heavy to do. On the other hand, querying on all Flag objects and iterating through them isn't cheap one.
Models.py
class Flag(models.Model):
flagger = models.ForeignKey(User, related_name='flaggers')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
object = GenericForeignKey('content_type', 'object_id')
submit_date = models.DateTimeField(auto_now_add=True)
reason = models.IntegerField(choices=REASON_CHOICES, default=BROKEN_LINK)
class Category(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
...
class Base(models.Model):
title = models.CharField(max_length=200)
slug = models.CharField(max_length=200, db_index=True)
category = models.ForeignKey(Category, default=1)
...
class Text(Base):
description = models.TextField(max_length=500)
...
class Image(Base):
image = models.ImageField()
class Post(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
created_at = models.DateTimeField(null=True)
After saving each of Text or Image instances, I create a Post model. So I can get either Posts flagged or text flagged and image flagged separately.
[also any tip about how can I define generic relation in case I should use it, would be appreciated]

Django GenericTabularInline: (admin.E302) 'ct_field' references 'content_type', which is not a field

I am trying to get a model (Prereq) with Generic Foreign Keys to appear in MyModel's admin.
models.py:
class MyModel(models.Model):
name = models.CharField(max_length=50, unique=True)
#etc
class Prereq(models.Model):
parent_content_type = models.ForeignKey(ContentType, related_name='prereq_parent')
parent_object_id = models.PositiveIntegerField()
parent_object = GenericForeignKey("parent_content_type", "parent_object_id")
prereq_content_type = models.ForeignKey(ContentType, related_name='prereq_item')
prereq_object_id = models.PositiveIntegerField()
prereq_object = GenericForeignKey("prereq_content_type", "prereq_object_id")
prereq_invert = models.BooleanField(default=False, help_text = 'parent is available if user does NOT have this pre-requisite')
or_prereq_content_type = models.ForeignKey(ContentType, related_name='or_prereq_item', blank=True, null=True)
or_prereq_object_id = models.PositiveIntegerField(blank=True, null=True)
or_prereq_object = GenericForeignKey("or_prereq_content_type", "or_prereq_object_id")
or_prereq_invert = models.BooleanField(default=False, help_text = 'parent is available if user does NOT have this pre-requisite')`
Admin.py:
class PrereqInline(GenericTabularInline):
model = Prereq
fk_name = "prereq_parent" #tried "parent_object" also
class MyModelAdmin(admin.ModelAdmin):
list_display = ('name', ...etc)
inlines = [
PrereqInline,
]
admin.site.register(MyModel, MyModelAdmin)
How to I solve the error I am getting?
<class 'my_app.admin.PrereqInline'>: (admin.E302) 'ct_field' references 'content_type', which is not a field on 'my_app.Prereq'.
Figured it out:
GenericTabularInline required the addition of ct_field and ct_fk_field because those fields did not use the default names of content_type and object_id respectively
class PrereqInline(GenericTabularInline):
model = Prereq
ct_field = "parent_content_type"
ct_fk_field = "parent_object_id"
fk_name = "parent_object"